Evercookies: модифицируем неубиваемые куки для ASP.NET

Программирование

Tagged Under : , , ,

Собственно, по поводу evercookies пишут многое: у кого-то работает у кого-то нет. Лично у меня всё нормально работает и информация в куках хранится и не сбрасывается. А значит почему не использовать?
Правда тут появляется ряд трудностей:

  • исходники нужных серверных страниц (png и etag’и) на php
  • необходимость ввести ряд правок в evercookie.js (например, разрешить загружать evercookie.swf с другого домен, на тот случай если есть CDN; отключить CSS History Knocker – 190 запросов к гуглу слишком много: сначала страница зависла, потом firefox вылетел); подправить пару функций для использования asp.net-страниц или хендлера


Во-первых, использовать две отдельные страницы как-то не резонно, тем более, что обе они предназначены для одного, поэтому я решил сделать хендлер (можно и страницу), которому в качестве параметров передавать имя куки и режим (png или etag). В итоге вместо evercookie_etag и evercookie_png стало EvercookieHandler.ashx?name=&emode=. Менее красиво, но не страшно: всегда можно прикрутить роутинг.
Ниже код метода ProcessRequest хендлера:

        public void ProcessRequest(HttpContext context)
        {
            string mode = "";
            HttpCookie cookie = context.Request.Cookies[context.Request["name"]];
            if ((mode = context.Request["emode"]) != null)
                switch (mode)
                {
                    case "png":
                        if (cookie == null)
                        {
                            context.Response.StatusCode = 304;
                        }
                        else
                        {
                            int x = 0;
                            using (Bitmap img = new Bitmap(200, 1))
                            {
                                for (int i = 0; i < cookie.Value.Length; i += 3)
                                {
                                    byte[] b = new byte[3];
                                    for (byte j = 0; j < 3; j++)
                                        if (i + j < cookie.Value.Length)
                                            b[j] = (byte)cookie.Value[i + j];
                                    Color c = Color.FromArgb(b[0], b[1], b[2]);
                                    img.SetPixel(x++, 0, c);
                                }
                                context.Response.AppendHeader("Content-Type", "image/png");
                                context.Response.AppendHeader("Last-Modified", DateTime.Now.ToString("R"));
                                context.Response.AppendHeader("Expires", DateTime.Now.AddYears(20).ToString("R"));
                                context.Response.AppendHeader("Cache-Control", "private, max-age=" + 24 * 365 * 60 * 60 * 20);
                                using (MemoryStream ms = new MemoryStream())
                                {
                                    img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                                    ms.WriteTo(context.Response.OutputStream);
                                }                                
                            }
                        }
                        break;
                    case "etag":
                        if (cookie != null)
                        {
                            context.Response.AppendHeader("ETag", cookie.Value);
                            context.Response.Write(cookie.Value);
                        }
                        else
                            if (context.Request.Headers["If-None-Match"] != null)
                                context.Response.Write(context.Request.Headers["If-None-Match"]);
                        break;
                }            
        }

Замечу, что на вебдеве etag не «взлетит» – видимо сервер не поддерживает данный заголовок, зато на IIS’е всё в норме.
Следующий пункт – модификация evercookie.js.
Прежде всего избавим от мучений гугл и избавим заодно от подвисания страницу. Делается это легко – в комментариях в самом файле прописано как это сделать:

var _ec_history = 0;

0 – выключаем данный функционал, 1 – включаем.
Далее находим определение функции evercookie_lso и немного модифицируем его:

var params = {};
params.swliveconnect = "true";
params.allowScriptAccess = "always"; // вот добавляемая строка
var attributes = {};

Теперь флешку можно подгружать и с других доменов.
Теперь осталось модифицировать evercookie_etag и evercookie_png

            this.evercookie_etag = function (name, value) {
            if (typeof (value) != "undefined") {
                // make sure we have evercookie session defined first
                document.cookie = name + '_etag=' + value;

                // evercookie_etag.php handles etagging
                var img = new Image();
                img.style.visibility = 'hidden';
                img.style.position = 'absolute';
                img.src = ajaxHandler + '?name=' + name + '_etag&emode=etag';
            }
            else {
                // interestingly enough, we want to erase our evercookie
                // http cookie so the php will force a cached response
                var origvalue = this.getFromStr(name + "_etag", document.cookie);
                self._ec.etagData = undefined;
                document.cookie = name + '_etag=; expires=Mon, 20 Sep 2010 00:00:00 UTC; path=/';

                $.ajax({
                    url: ajaxHandler + '?name=' + name + '_etag&emode=etag',
                    success: function (data) {
                        // put our cookie back

                        document.cookie = name + '_etag=' + origvalue + '; expires=Tue, 31 Dec 2030 00:00:00 UTC; path=/';
                       
                        self._ec.etagData = data;
                    }
                });
            }
        }

        this.evercookie_png = function (name, value) {
            if (document.createElement('canvas').getContext) {
                if (typeof (value) != "undefined") {
                    // make sure we have evercookie session defined first
                    document.cookie = name + '_png=' + value;

                    // evercookie_png.php handles the hard part of generating the image
                    // based off of the http cookie and returning it cached
                    var img = new Image();
                    img.style.visibility = 'hidden';
                    img.style.position = 'absolute';
                    img.src = ajaxHandler + '?name=' + name + '_png&emode=png';
                }
                else {
                    var context = document.createElement('canvas');
                    context.style.visibility = 'hidden';
                    context.style.position = 'absolute';
                    context.width = 200;
                    context.height = 1;
                    var ctx = context.getContext('2d');

                    // interestingly enough, we want to erase our evercookie
                    // http cookie so the php will force a cached response
                    var origvalue = this.getFromStr(name + "_png", document.cookie);
                    self._ec.pngData = undefined;
                    document.cookie = name + '_png=; expires=Mon, 20 Sep 2010 00:00:00 UTC; path=/';

                    var img = new Image();
                    img.style.visibility = 'hidden';
                    img.style.position = 'absolute';
                    img.src = ajaxHandler + '?name=' + name + '_png&emode=png';

                    img.onload = function () {
                        // put our cookie back
                        document.cookie = name + '_png=' + origvalue + '; expires=Tue, 31 Dec 2030 00:00:00 UTC; path=/';

                        self._ec.pngData = '';
                        ctx.drawImage(img, 0, 0);

                        // get CanvasPixelArray from  given coordinates and dimensions
                        var imgd = ctx.getImageData(0, 0, 200, 1);
                        var pix = imgd.data;

                        // loop over each pixel to get the "RGB" values (ignore alpha)
                        for (var i = 0, n = pix.length; i < n; i += 4) {
                            if (pix[i] == 0) break;
                            self._ec.pngData += String.fromCharCode(pix[i]);
                            if (pix[i + 1] == 0) break;
                            self._ec.pngData += String.fromCharCode(pix[i + 1]);
                            if (pix[i + 2] == 0) break;
                            self._ec.pngData += String.fromCharCode(pix[i + 2]);
                        }
                    }
                }
            }
        }

Собственно, внесённые в эти методы изменения вызваны созданием хендлера вместо php-страниц.




Оставить комментарий