Source: Cookies/cookies.js

/**
 * @class
 * @classdesc Plugin to manage cookies.
 * @name Cookies
 * @memberOf ATInternet.Tracker.Plugins
 * @type {function}
 * @param parent {object} Instance of the Tag used
 * @description
 * This plugins allow the storage of elements like string, array or object
 * in cookies. So it's important to know that it uses serialization. For that reason
 * the values must be <i>JSON</i> compatible
 * @public
 */
window['ATInternet']['Tracker']['Plugins']['Cookies'] = function (parent) {
    "use strict";
    var self = this;

    // Cache of items to avoid useless search in cookies.
    var _itemCache = {};

    /**
     *
     *  Base64 encode / decode
     *  http://www.webtoolkit.info/
     *
     **/
    var Base64 = {

        // private property
        _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

        // public method for encoding
        encode: function (input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;

            input = Base64._utf8_encode(input);

            while (i < input.length) {

                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);

                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;

                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                }

                output = output +
                    this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                    this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

            }

            return output;
        },

        // public method for decoding
        decode: function (input) {
            var output = "";
            var chr1, chr2, chr3;
            var enc1, enc2, enc3, enc4;
            var i = 0;

            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

            while (i < input.length) {

                enc1 = this._keyStr.indexOf(input.charAt(i++));
                enc2 = this._keyStr.indexOf(input.charAt(i++));
                enc3 = this._keyStr.indexOf(input.charAt(i++));
                enc4 = this._keyStr.indexOf(input.charAt(i++));

                chr1 = (enc1 << 2) | (enc2 >> 4);
                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                chr3 = ((enc3 & 3) << 6) | enc4;

                output = output + String.fromCharCode(chr1);

                if (enc3 != 64) {
                    output = output + String.fromCharCode(chr2);
                }
                if (enc4 != 64) {
                    output = output + String.fromCharCode(chr3);
                }

            }

            output = Base64._utf8_decode(output);

            return output;

        },

        // private method for UTF-8 encoding
        _utf8_encode: function (string) {
            string = string.replace(/\r\n/g, "\n");
            var utftext = "";

            for (var n = 0; n < string.length; n++) {

                var c = string.charCodeAt(n);

                if (c < 128) {
                    utftext += String.fromCharCode(c);
                }
                else if ((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                }
                else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                }

            }

            return utftext;
        },

        // private method for UTF-8 decoding
        _utf8_decode: function (utftext) {
            var string = "";
            var i = 0;
            var c, c2, c3;
            c = c2 = c3 = 0;

            while (i < utftext.length) {

                c = utftext.charCodeAt(i);

                if (c < 128) {
                    string += String.fromCharCode(c);
                    i++;
                }
                else if ((c > 191) && (c < 224)) {
                    c2 = utftext.charCodeAt(i + 1);
                    string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                    i += 2;
                }
                else {
                    c2 = utftext.charCodeAt(i + 1);
                    c3 = utftext.charCodeAt(i + 2);
                    string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                    i += 3;
                }

            }

            return string;
        }

    };

    /**
     * Returns the value of a cookie (decoded with Base64.decode)
     * @name getCookie
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param name {string} Name of the cookie wanted
     * @return string {string} Return null if the cookie does not exist
     * @public
     */
    var _getCookie = self['getCookie'] = function (name) {
        if (!parent['getConfig']('disableCookie') && name && typeof name === 'string') {
            /* @if debug */
            parent.debug('Cookies:ReadCookie:FromBrowser', 'DEBUG', '', {name: name});
            /* @endif */
            var cookies = document.cookie,
                re = new RegExp('(?:^| )' + name + '=([^;]+)'),
                result = re.exec(cookies) || null;
            if (result) {
                return _decodeValue(result[1]);
            }
        }
        return null;
    };

    /**
     * Create, update or erase a cookie
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @name setCookie
     * @function
     * @param name {string} Name of the cookie that you wan to create/erase
     * @param value {string} Value that you need to cookies (encoded with Base64.encode)
     * @param options {JSON} Object which contains all cookies options (expiration date/ageInSecond, domain, path and secure flag)
     * @return boolean|null {boolean|null} Return true if the cookie has been set and really exists, return false if the cookie has been set but does not exists, returns null if the cookie hasn't been set at all (mostly a parameter with an error)
     * @example
     * <pre><code class="javascript">setCookie('name','value',{end:'3600',domain:'.site.com',path:'/',secure:true});   // try to set a secure cookie on '.site.com' with path '/' for 3600seconds
     * setCookie('name','value');                                                        // try to set a cookie on current domain/path for the session
     * var date = new Date;
     * date.setMinutes(date.getMinutes()+30);
     * setCookie('name','value',{end:date,domain:'.site.com'});   // try to set a cookie on '.site.com' for 30minutes
     * </code></pre>
     * The third parameter <i>options</i> can contain 4 properties which are :
     * <pre><code class="javascript">{
     *      end : expiration_date,      // if a number, it represents a time in second like the first example. If a date, see the previous example.
     *      session : expiration_date,  // OVERWRITE end ! indicates the expiration date must be delayed each time the element is read. It's a number representing a time in seconds
     *      domain : '.domain.com',     // allow to specify a domain
     *      path : '/',                 // allow to specify a path
     *      secure : 'boolean'          // allow to set a secure cookie (readable only in HTTPS pages)
     *  }
     * </code></pre>
     * @public
     */
    var _setCookie = self['setCookie'] = function (name, value, options) {
        if (!parent['getConfig']('disableCookie') && name && typeof name === 'string' && typeof value === 'string') {
            /* @if debug */
            parent.debug('Cookies:SetCookie', 'DEBUG', '', {name: name, value: value, options: options});
            /* @endif */
            var domain_config = parent['getConfig']('cookieDomain'),
                str = name + '=' + _encodeValue(value);
            var secureOpt = false;
            if (options) {
                secureOpt = options['secure'];
                var end = options['end'],
                    domainOpt = options['domain'],
                    path = options['path'];
                str += end ? (typeof end.toGMTString === 'function' ? ';expires=' + end.toGMTString() : (typeof end === 'number' ? ';max-age=' + end.toString() : '')) : '';
                str += (domain_config || domainOpt ) ? ';domain=' + (domainOpt ? domainOpt : domain_config) : '';
                str += path && typeof path === 'string' ? ';path=' + path : '';
            }
            var secure = parent['getConfig']('cookieSecure') || secureOpt;
            str += secure && typeof secure === 'boolean' ? ';secure' : '';
            document.cookie = str;
            // return a boolean which indicates if the cookie exists
            return true;
        } else {
            return null;
        }
    };

    /**
     * Encode value depending on configuration mode
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param value {*} element to encode
     * @return {string}
     * @private
     */
    var _encodeValue = function (value) {
        if(!!parent['getConfig']('base64Storage')) {
            return Base64.encode(value);
        }
        return encodeURIComponent(value);
    };

    /**
     * Decode value depending on configuration mode
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param value {*} element to decode
     * @return {string}
     * @private
     */
    var _decodeValue = function (value) {
        if(!!parent['getConfig']('base64Storage')) {
            return Base64.decode(value);
        }
        return decodeURIComponent(value);
    };

    /**
     * Create an item to store
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param name {string} Name of the Item
     * @param val {*} element to stock, JSON compatible
     * @param options {object} Object which contains all cookies and items options
     * @return {object}
     * @private
     */
    var _makeItem = function (name, val, options) {
        var item = {};
        item['name'] = name;
        item['val'] = val;

        if (options['session'] && typeof options['session'] === 'number') {
            options['end'] = options['session'];
        }
        item['options'] = options;
        return item;
    };

    /**
     * Parse the cookie got in an object (item)
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param name {string} Name of the cookie
     * @return {object}
     * @private
     */
    var _getItem = function (name) {
        var item = null;
        var sItem = _getCookie(name);

        if (sItem) {
            item = ATInternet['Utils']['jsonParse'](sItem);
        }
        return item;
    };

    /**
     * Get a specific property from an object (item)
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param item {object} cookie json-parsed
     * @param prop {string} Name of the property
     * @return {*}
     * @private
     */
    var _getItemProperty = function (item, prop) {
        var value = null;
        if (item && typeof item['val'] === 'object' && !(item['val'] instanceof Array) && item['val'][prop] !== undefined) {
            value = item['val'][prop];
        }
        return value;
    };

    /**
     * Stock an object (item) in a cookie of the same name. Replace the cookie if exists.
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param item {object} cookie json-parsed
     * @return {object|null}
     * @private
     */
    var _setItem = function (item) {
        if (_setCookie(item['name'], ATInternet['Utils']['jsonSerialize'](item), item['options'])) {
            return item;
        } else {
            return null;
        }
    };

    /**
     * Set or update the property of an object (item).
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param item {object} cookie json-parsed
     * @param prop {string} name of the property
     * @param val {*} value of the property
     * @return {object|null}
     * @private
     */
    var _setItemProperty = function (item, prop, val) {
        if (item && typeof item['val'] === 'object' && !(item['val'] instanceof Array)) {
            item['val'][prop] = val;
            return item;
        }
        return null;
    };

    /**
     * Get the value of an object (item) or a property either from a cookie or from the cache. Begin to search in the cache and then
     * in the cookie if it doesn't find.
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param name {string} name of the element
     * @param prop {string} name of the property
     * @param byPassCache {boolean} If true, the value will be directly retrieved from the cookie
     * @return {*}
     * @private
     */
    var _get = function (name, prop, byPassCache) {
        var item = null;

        //first we retrieve the item
        // we try to get the item in the cache first
        if (!byPassCache && _itemCache[name]) {
            item = _itemCache[name];
            /* @if debug */
            parent.debug('Cookies:ReadCookie:FromCache', 'DEBUG', '', {name: name, cache: _itemCache});
            /* @endif */
        } else {
            item = _getItem(name);
            if (item) {
                //if options session is valid we must extend the expiration
                //by rewriting the cookie
                if (item['options']['session']) {
                    _setItem(item);
                }
                //put it on the cache
                _itemCache[name] = item;
            }
        }

        if (item) {
            if (prop) {
                //if prop is defined we return it if exist
                return _getItemProperty(item, prop);
            } else {
                //or we return the value of the item
                return item['val'];
            }
        } else {
            return null;
        }
    };

    /**
     * Create, update or erase an object or one of its properties
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param name {string} name of the cookie
     * @param prop {string|null} name of the property (may be null)
     * @param val {*} value of the element or of the property if exists
     * @param options {object} Object which contains options. Useless if you create or update a property.
     * @return {*}
     * @private
     */
    var _set = function (name, prop, val, options) {
        var item = null;
        if (prop) {
            item = _getItem(name);
            if (item) {
                item = _setItemProperty(item, prop, val);
                if (item) {
                    item = _setItem(item);
                }
            }
        } else {
            options = options || {};
            item = _makeItem(name, val, options);
            item = _setItem(item);
        }

        if (item) {
            _itemCache[name] = item;
        }

        if (item) {
            return item['val'];
        }
        return null;
    };

    /**
     * Delete an object or a property of it in the concerned cookie
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param name {string} name of the cookie
     * @param prop {string} name of the property
     * @private
     */
    var _del = function (name, prop) {
        if (prop) {
            _set(name, prop, undefined);
        } else {
            _itemCache[name] = undefined;
            _setCookie(name, '', {end: 0});
        }
    };

    /**
     * Retrieve a cookie or a property of it.
     * @name get
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param key {string|Array} If a string, the cookie's name, if an array the first element is the cookies's name and the second is the property's name
     * @param [byPassCache] {boolean} If true, the value will be directly retrieved from the cookie
     * @return * {*} Return null if the element does not exist
     * @public
     */
    var get = self['get'] = function (key, byPassCache) {
        byPassCache = byPassCache || false;
        if (key instanceof Array && key.length === 2) {
            return _get(key[0], key[1], byPassCache);
        } else {
            return _get(key, undefined, byPassCache);
        }
    };

    /**
     * Same as the 'get' method but for a private cookie linked to the current numsite.
     * @name getPrivate
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param key {string|Array} If a string, the cookie's name, if an array the first element is the cookies's name and the second is the property's name
     * @param [byPassCache] {boolean} If true, the value will be directly retrieved from the cookie
     * @return * {*} Return null if the element does not exist
     * @public
     */
    self['getPrivate'] = function (key, byPassCache) {
        byPassCache = byPassCache || false;
        if (key instanceof Array) {
            key[0] = key[0] + parent.getConfig('site');
        } else {
            key = key + parent.getConfig('site');
        }
        return get(key, byPassCache);
    };

    /**
     * Create, update or erase a cookie or the property of it.
     * @name set
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param key {string|Array} If a string, name the element you wan to create/update, if an array the first element is the cookie's name and the second is a property of it
     * @param value {*} Value to save
     * @param options {object} Object which contains options. Useless if you create or update a property.
     * @return boolean|null {boolean|null} Return true if the cookie has been set and really exists, return false if the cookie has been set but does not exists, returns null if the cookie hasn't been set at all (mostly a parameter with an error)
     * @example
     * <pre><code class="javascript">set('cookie','value',{end:'3600',domain:'.site.com',path:'/',secure:true});
     * set('cookie',[1,2,3],{end:'3600'});
     * set('cookie',{'myvar1':1,'myvar2':2},{session:'3600'});
     * set(['cookie','myvar2'],3);
     *
     * var date = new Date;
     * date.setMinutes(date.getMinutes()+30);
     * set('cookie','value',{end: date.getMinutes()+30});
     * </code></pre>
     * @public
     */
    var set = self['set'] = function (key, val, options) {
        if (key instanceof Array) {
            return _set(key[0], key[1], val);
        } else {
            return _set(key, null, val, options);
        }
    };

    /**
     * Same as the 'set' method but for a private cookie linked to the current numsite
     * @name setPrivate
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @public
     */
    self['setPrivate'] = function (key, val, options) {
        if (key instanceof Array) {
            key[0] = key[0] + parent.getConfig('site');
        } else {
            key = key + parent.getConfig('site');
        }
        return set(key, val, options);
    };

    /**
     * Delete a cookie or a property of it
     * @name del
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param key {string|Array} If a string, name the element you wan to create/update, if an array the first element is the cookie's name and the second is a property of it
     * @public
     */
    var del = self['del'] = function (key) {
        if (key instanceof Array) {
            return _del(key[0], key[1]);
        } else {
            return _del(key);
        }
    };

    /**
     * Same as the 'del' method but for a private cookie linked to the current numsite
     * @name delPrivate
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @param key {string|Array}
     * @public
     */
    self['delPrivate'] = function (key) {
        if (key instanceof Array) {
            key[0] = key[0] + parent.getConfig('site');
        } else {
            key = key + parent.getConfig('site');
        }
        return del(key);
    };

    /**
     * Clear the cache
     * @name cacheInvalidation
     * @memberof ATInternet.Tracker.Plugins.Cookies#
     * @function
     * @public
     */
    self['cacheInvalidation'] = function () {
        /* @if debug */
        parent.debug('Cookies:ClearCache', 'DEBUG', '');
        /* @endif */
        _itemCache = {};
    };

    // For unit tests on private elements !!!
    /* @if test */
    self['Base64'] = function () { return Base64 };
    self['_getCookie'] = _getCookie;
    self['_setCookie'] = _setCookie;
    self['_getCache'] = function () {
        return _itemCache
    };
    /* @endif */
};

window['ATInternet']['Tracker']['addPlugin']('Cookies');