создание и продвижение сайта (вбиваю в ТОП-10, как гвозди)Настройка и сопровождение платной рекламы яндекс.директ

8. Программируем "за кадром". JavaScript (от intuit.ru)

Рассматриваются приемы программирования на JavaScript, невидимые для читателей HTML-страниц: механизм cookie, управление фокусом, скрытая передача данных, вопросы безопасности.

Механизм cookie

Cookie являются механизмом управления обменом данных. Основная их функция - поддержка сеанса работы между клиентом (браузером) и сервером.

Что такое cookie

Формально, cookie (читается: куки; не склоняется) - это небольшой фрагмент данных, которые веб-браузер пересылает веб-серверу в HTTP-запросе при каждой попытке открыть очередную страницу сайта. Обычно куки создаются веб-сервером и присылаются в браузер при первом запросе к сайту. Куки также могут быть созданы самой загруженной web-страницей, а именно имеющимся в ней скриптом JavaScript. Далее хранятся на компьютере пользователя в виде текстового файла, до тех пор, пока либо не закончится их срок, либо они будут удалены скриптом или пользователем. Имеются ограничения на объем и количество хранимых cookie, они зависят от браузера, но есть минимальные требования. Спецификация cookie описана в RFC 2965. На практике cookie обычно используются для:

  • аутентификации пользователя;
  • хранения персональных предпочтений и настроек пользователя;
  • отслеживания состояния сессии доступа пользователя;
  • ведения статистики о пользователях.

    Без куки просмотр каждой веб-страницы является изолированным действием, не связанным с просмотром других страниц того же сайта. В конце этого раздела мы создадим страницы, передающие друг другу информацию с помощью куки.

    Главными атрибутами cookie являются: имя/значение куки, срок действия, путь, доменное имя, шифрование. Атрибуты записываются через точку с запятой. Обязательным является лишь имя/значение. Например:

    customer=21584563;expires=Fri, 31-Dec-2010 23:59:59 GMT;
    path=/;domain=1.ru;secure
    

    Здесь customer - имя куки; 21584563 - значение куки; expires=… - срок действия куки; path=/ - путь, для которого действует эта куки (в данном случае куки действуют для любой страницы на данном сайте); domain=1.ru - домен (сайт), для которого будет действовать эта куки; secure - использовать ли для передачи куки шифрованный канал (HTTPS). Если не указан expires, то куки действительна до закрытия браузера. Если не указаны path и domain, то куки действительна для текущей web-страницы. Удаляются куки путем указания истекшего срока действия (браузер сам сотрет такие куки по окончании своей работы). Здесь же мы остановимся на методах работы с cookie с помощью инструментов JavaScript.

    Чтение cookie

    Для работы с куки из сценария JavaScript используется свойство document.cookie. Следующая команда покажет все установленные куки:

    alert(document.cookie)

    Она выдаст нечто вроде: name1=value1; name2=value2; …, т.е. перечисление пар имя=значение, разделенных точкой с запятой и пробелом. Как видите, нам показывают только имена и значения куки; другие атрибуты куки (срок действия, домен и т.д.) через свойство document.cookie недоступны.

    Но обычно требуется больше - узнать, установлено ли значение конкретной куки, и если установлено, то прочитать его. Значит, нужно разобрать полученную выше строку с помощью методов работы со строковыми объектами. Для этого создадим 2 функции: existsCookie - проверяет, имеется ли куки с данным именем; CookieValue - возвращает значение куки по ее имени:

    function existsCookie(CookieName)
    {//Узнает, имеется ли куки с данным именем
    return (document.cookie.split(CookieName+'=').length>1)}
    function CookieValue(CookieName)
    {//Выдает значение куки с данным именем
    var razrez=document.cookie.split(CookieName+'=');
    if(razrez.length>1)
    {//Значит, куки с этим именем существует
    var hvost=razrez[1],
    tzpt=hvost.indexOf(';'),
    EndOfValue=(tzpt>-1)? tzpt: hvost.length;
    return unescape(hvost.substring(0,EndOfValue))}}
    

    Мы воспользовались тем, что пары имя/значение разделены точкой с запятой, а значение куки не может содержать символ "точка с запятой" (";"). На самом деле, если произвести попытку установить куки со значением, содержащим этот символ, то в результате значение будет обрезано до первого вхождения этого символа. Попутно заметим, что в целях совместимости не рекомендуется использовать в значении куки символы: точка с запятой, пробел, равенство - если они все же требуются, их следует заменить на %3B, %20 и %3D, соответственно. Проще всего при создании куки пользоваться функцией escape(), которая и произведет все эти преобразования, а при чтении куки - обратной функцией unescape(), что мы и сделали выше.

    Создание или изменение cookie

    Далее мы хотим создавать или менять cookie. Разработчики языка JavaScript позаботились о web-программистах и реализовали свойство document.cookie довольно интеллектуально. Если выполнить простую команду присвоения:

    document.cookie='ИмяКуки=Значение; expires=дата; path=путь; domain=домен; secure';
    

    то прежние хранившиеся куки не будут стерты, как можно заподозрить. Вместо этого браузер проверит, не имеется ли уже в document.cookie куки с именем ИмяКуки. Если нет, то новая куки будет добавлена в document.cookie; если да, то для куки с этим именем будут обновлены указанные в команде параметры (значение, срок действия и т.д.). Это поведение куки демонстрирует следующий пример:

    document.write(document.cookie+'<p>');
    document.cookie='basket=SiemensA35';
    document.write(document.cookie+'<p>');
    document.cookie='customer=Ivanov';
    document.write(document.cookie+'<p>');
    document.cookie='basket=Nokia3310';
    document.write(document.cookie+'<p>');
    

    Напишем универсальную функцию для задания куки, которой Вы можете пользоваться на практике. 1-е 2 ее аргумента (name и value) обязательны, остальные необязательны. В ней используется функция escape(), которая преобразует специальные символы в их коды, например, пробел в %20, равенство в %3D и т.д.

    function setCookie(name, value, exp, pth, dmn, sec)
    {//Создает cookie с указанными параметрами
    document.cookie=name+'='+escape(value)
    +((exp)?';expires='+exp: '')
    +((pth)?';path='+pth: '')
    +((dmn)?';domain='+dmn: '')
    +((sec)?';secure': '');}
    

    Остается научиться передавать этой функции время истечения срока действия куки в правильном формате (пример см. выше). В этом нам поможет метод toGMTString() объекта Date. На практике заранее известно не время истечения срока действия куки, а сам срок действия (в днях, часах, минутах и т.д.), отсчитывая от текущего момента. Напишем функцию, которая по этим данным возвращает точный момент времени, причем в нужном нам формате:

    function TimeAfter(d,h,m)
    {//Выдает время через d дней h часов m минут
    var now=new Date(),//объект класса Data
    nowMS=now.getTime(),//в миллисекундах (мс)
    newMS=((d*24+h)*60+m)*60*1000+nowMS;
    now.setTime(newMS);//новое время в мс
    return now.toGMTString();}
    

    Удаление cookie

    Удалить куки - значит в качестве времени истечения куки указать какой-либо прошлый момент времени, например, "сутки назад". Напишем соответствующую функцию:

    function deleteCookie(CookieName)
    {//Удаляет куки с данным именем
    setCookie(CookieName,'',TimeAfter(-1,0,0));}
    

    Демонстрационный пример

    Теперь у нас есть весь арсенал функций работы с куки; сохраните их единый в файл cookies.js. Мы создадим 2 независимые web-страницы, которые благодаря куки смогут обменяться информацией. Первая страница запрашивает имя пользователя и записывает его в куки сроком на 1 минуту.

    <SCRIPT SRC='cookies.js'></SCRIPT>
    <SCRIPT>
    setCookie('customername',
    prompt('Введите ваше имя',''),
    TimeAfter(0,0,1));
    alert('Мы Вас запомнили!')
    </SCRIPT>
    

    Пример 8.1. Запись имени пользователя в cookie

    Вторая страница приветствует пользователя по имени, которое она прочитает из cookie. Убедитесь, что она узнает пользователя, даже если перед открытием страницы закрыть браузер. Однако если открыть эту страницу через 1 минуту, то она уже не сможет узнать пользователя.

    <SCRIPT SRC=cookies.js></SCRIPT>
    <SCRIPT>
    if(existsCookie('customername'))
    alert('Приветствуем Вас, '+CookieValue('customername')+'!');
    else alert('Извините, мы Вас уже не помним…')
    </SCRIPT>
    

    Пример 8.2. Чтение имени пользователя из cookie

    Управление фокусом

    Фокус - это характеристика текущего окна, фрейма или поля формы. В каждом из разделов, описывающем программирование этих объектов, мы, так или иначе, касаемся вопроса фокуса. Под фокусом понимают возможность активизации свойств и методов объекта. Например, окно в фокусе, если оно является текущим, т.е. лежит поверх всех других окон и исполняются его методы или можно получить доступ к его свойствам.

    В данном разделе мы рассмотрим управление фокусом в

  • окнах;
  • фреймах;
  • полях формы.

    Следует сразу заметить, что фреймы - это тоже объекты класса Window, и многие решения, разработанные для окон, справедливы и для фреймов.

    Управляем фокусом в окнах

    Для управления фокусом у объекта класса "окно" существует 2 метода: focus() и blur(). Первый передает фокус в окно, в то время как 2-й фокус из окна убирает. Рассмотрим простой пример:

    <SCRIPT>
    function open_hidden_window()
    {wid=window.open('','test', 'width=100,height=100');
    wid.opener.focus();
    wid.document.open();
    wid.document.write('<H1>Скрытое</H1>');
    wid.document.close()}
    </SCRIPT>
    <INPUT TYPE=button value='Скрытое окно'
    onClick='open_hidden_window()'>
    

    В данном примере новое окно открывается и сразу теряет фокус - прячется за окном-родителем. При первом нажатии на кнопку оно еще всплывает и только после этого прячется, но при повторном нажатии пользователь не видит появления нового окна, т.к. оно уже открыто и меняется только его содержимое.

    Для того чтобы этого не происходило, нужно после открытия передавать фокус новому окну:

    <SCRIPT>
    function open_visible_window()
    {wid=window.open('','test', 'width=100,height=100');
    wid.focus();
    wid.document.open();
    wid.document.write('<H1>Видимое</H1>');
    wid.document.close()}
    </SCRIPT>
    <INPUT TYPE=button value='Видимое окно'
    onClick='open_visible_window()'>
    

    Если теперь совместить эти 2 примера на 1 странице и нажимать попеременно кнопки "Скрытое окно" и "Видимое окно", то окно будет то появляться, то исчезать. При этом новых окон не появляется, так как с одним и тем же именем может быть открыто только одно окно.

    Невидимое окно может доставить пользователю неприятности, из которых самая безобидная - отсутствие реакции на его действия. Код просто записывается в невидимое окно. Но ведь в скрытом окне можно что-нибудь и запустить. Для этого стоит только проверить, существует ли данное окно или нет, и если оно есть и не в фокусе, то активизировать в нем какую-нибудь программу.

    Для реализации такого сценария достаточно использовать метод окна onblur(). Мы воспользуемся этим методом "в лоб":

    window.onblur=new
    Function("window.defaultStatus='Работаем в фоне…';");
    window.onfocus=new
    Function("window.defaultStatus='Готово';");
    

    Этот пример демонстрирует возможность выполнения функции в фоновом режиме. Для проверки этого примера откройте его в браузере, а затем откройте любое другое окно небольшого размера (например, блокнот) и следите за полем статуса в открытом окне браузера. Аналогичного эффекта можно было достичь, поместив указанные команды в контейнере BODY в обработчиках событий onBlur и onFocus.

    Конечно, когда разработчики создавали всю эту конструкцию, думали не о том, как насолить пользователю, а о том, как сократить ресурсы, необходимые браузеру для отображения нескольких окон. Ведь можно выполнить все то же самое с точностью до наоборот: запускать, например, часы в фокусе и останавливать их в фоне. Но этот пример мы рассмотрим в контексте фреймов.

    Управление фокусом во фреймах

    Фрейм - это такое же окно, как и само окно браузера. Точнее, это объект того же класса. К нему применимы те же методы, что и к обычному объекту "окно".

    Пример 8.1 (кликните, чтобы открыть). Рассмотрим страницу из 2 одинаковых фреймов:

    <FRAMESET COLS='50%,*'>
    <FRAME SRC=clock.htm>
    <FRAME SRC=clock.htm>
    </FRAMESET>
    

    Пример 8.3. Часы в 2 фреймах (работают там, где фокус)

    В файл clock.htm поместим следующую страницу, которая тем самым будет отображаться в обоих фреймах:

    <SCRIPT>
    var flag=false;
    function clock()
    {if(flag)
    {var d=new Date();
    document.f.e.value=
    d.gethours()+':'+
    d.getMinutes()+':'+
    d.getSeconds()}
    setTimeout('clock();',100)}
    </SCRIPT>
    <BODY onLoad='clock()'
    onFocus='this.flag=true'
    onBlur='this.flag=false'>
    <FORM NAME=f>
    <INPUT NAME=e>
    </FORM>
    

    Пример 8.4. Часы запускаются, если данное окно в фокусе

    Если кликнуть на любом из 2 фреймов, то пойдут часы именно в этом фрейме, а в другом фрейме, если они уже были запущены ранее, остановятся.

    Фокус в полях формы

    Обработчики событий onFocus и onBlur определены для любого поля формы (текстовое однострочное поле, текстовая область, кнопка, радиокнопки, ниспадающие списки). В качестве одного из возможных применений этих обработчиков рассмотрим защиту поля от ввода (изменения) содержимого:

    <INPUT TYPE=text onFocus=this.blur() VALUE='Попробуйте изменить'>
    

    Когда пользователь попытается установить курсор в таком поле ввода, тем самым передав ему фокус, наш обработчик события уведет из него фокус и тем самым изменение поля становится невозможно.

    Примечание. Этот пример приведен лишь в качестве демонстрации обработчиков событий. В настоящее время для защиты поля от изменения достаточно указать у него атрибут READONLY, т.е. предыдущий пример равносилен следующему:

    <INPUT TYPE=text READONLY VALUE='Попробуйте изменить'>
    

    Скрытая передача данных из форм

    Обмен данными в Web-технологии подробно рассматривается в курсе "Введение в CGI". Здесь же мы рассмотрим возможности передачи скрытых от пользователя данных с использованием JavaScript. Начнем с простого примера.

    Пример 8.2. Введите в поле имя и нажмите кнопку:

    <FORM NAME=f>Имя:
    <INPUT TYPE=text NAME=user>
    <INPUT TYPE=hidden NAME=h>
    <INPUT TYPE=submit VALUE=Отправить>
    </FORM>
    <SCRIPT>
    document.f.h.value=window.navigator.appName
    </SCRIPT>
    

    Пример 8.5. Отправка данных из скрытых полей формы

    После нажатия кнопки в адресной строке вы увидите, что помимо user=имя имеется также h=имя_вашего_браузера. В заполненной Вами форме поля h не было видно. Таким образом, форма передала на сервер дополнительную информацию помимо Вашего желания. Это уже неприятно, хотя сама информация в данном случае (имя браузера) не представляет из себя ничего криминального. Если бы в качестве метода передачи данных был использован не GET (как в нашем примере, по умолчанию), а POST, то этой скрытой передачи данных пользователь даже не заметил бы.

    Пример 8.3. Пример состоит из 2 фреймов (расположенных в файлах left.htm и right.htm), в которые помещена одинаковая форма. В правый фрейм, помимо этого, помещен также следующий скрипт:

    function copyFields()
    {here=document.forms[0].elements;
    there=window.top.frames[0].document.forms[0].elements;
    here[0].value=there[0].value;
    here[1].value=there[1].value;
    here[2].value=there[2].value;
    here[3].value=there[3].value;
    setTimeout('copyFields()',100)}
    window.onload=copyFields;
    

    Функция copyFields() запускается раз в 0,1 сек. Когда пользователь вводит данные в левом фрейме, эти же данные попадают в соответствующие поля правого фрейма. Таким образом, данные из одного окна могут быть считаны программой из другого окна (или фрейма). Вопрос только в том, хотите ли вы, чтобы это происходило. Как решаются эти вопросы, рассказано ниже в разделе "Модель безопасности".

    Еще один пример - отправка данных по событию без наличия видимой формы на web-странице:

    <FORM NAME=f MEthOD=post ACTION="javascript:alert('Передали данные');">
    <INPUT NAME=h TYPE=hidden>
    </FORM>
    <SCRIPT>
    document.f.h.value=window.navigator.appName
    </SCRIPT>
    <A HREF=javascript:alert('Просто ссылка') onClick="document.f.submit();">Нажми на ссылку</A>
    

    При нажатии на гипертекстовую ссылку произойдет не только выдача сообщения, которое в этой ссылке указано, но и отправка данных формы. В итоге вы получите 2 окна предупреждения. Но второе окно вы не заказывали!

    Конечно, бесконтрольной передачи данных на сервер можно избежать, введя режим подтверждения отправки (в настройках браузера). Но, во-первых, многие пользователи его отключают, а во-вторых, можно использовать не формы, а, например, графику. И эту возможность мы сейчас и рассмотрим в следующем разделе.

    Невидимый код

    Вопрос доступности JavaScript-кода рассматривается с 2 точек зрения: идентификация, как следствие - необходимость сокрытия кода, и безопасность пользователя, следовательно - доступность кода.

    Приемы программирования со скрытым кодом позволяют решить еще несколько задач, которые не связаны с безопасностью.

    Мы будем рассматривать возможности использования скрытого кода, не вынося вердиктов о преимуществе того или иного подхода. Рассмотрим несколько вариантов:

  • невидимый фрейм;
  • код во внешнем файле;
  • обмен данными посредством встроенной графики.

    Строго говоря, 1-е 2 варианта не скрывают код полностью. Они рассчитаны либо на неопытных пользователей, либо на нелюбопытных. Так или иначе, не каждый же раз вы будете смотреть исходный текст страницы.

    Невидимый фрейм

    Технология программирования в невидимом фрейме основана на том, что при описании фреймовой структуры можно задать конфигурацию типа:

    <FRAMESET COLS="100%,*">
    <FRAME SRC=left.htm>
    <FRAME SRC=right.htm>
    </FRAMESET>
    

    Пример 8.6. Правый фрейм имеет нулевую ширину (граница видима)

    В этом случае левый фрейм займет весь объем рабочей области окна, а содержание правого будет скрыто. Именно в этом невидимом фрейме мы и разместим код программы (например, приведенный выше скрипт считывания полей из формы в левом фрейме). В невидимый фрейм иногда помещают функции подкачки графики, позволяя пользователю уже работать с основным фреймом, пока грузится остальная часть графики.

    В данном примере, однако, мы оставили пользователю возможность разоблачить нас: вдоль правой границы окна видна вертикальная полоска - это граница между фреймами. Пользователь может подвинуть ее влево и увидеть правый фрейм. Защититься от этого несложно - достаточно задать толщину границ фреймов, а также указав невозможность изменять размеры у каждого фрейма:

    <FRAMESET COLS="100%,*">
    <FRAME NORESIZE SRC=left.htm>
    <FRAME NORESIZE SRC=right.htm>
    </FRAMESET>
    

    Пример 8.7. Правый фрейм имеет нулевую ширину (граница невидима)

    Код во внешнем файле

    О том, как подключать код JavaScript, размещенный во внешнем файле, рассказывалось во вводной лекции:

    <SCRIPT SRC="myscript.js"></SCRIPT>
    

    Данный способ позволяет скрыть код лишь от ленивого пользователя. Но сам код JavaScript легко доступен, т.к. указанный файл можно просто скачать отдельно, либо сохранить всю HTML-страницу (со всеми подключенными к ней скриптами) с помощью меню браузера.

    Обмен данными посредством встроенной графики

    Данный прием основан на 2 идеях: возможности подкачки графического образа без перезагрузки страницы и возможности подкачки этого графического образа не через указание URL графического файла, а через CGI-скрипт, который возвращает Content-type: image/… или осуществляет перенаправление. При этом следует учитывать, что использовать метод, отличный от GET, можно только в формах. В следующем примере мы создали функцию change_image(), которая формально говоря меняет значение свойства src картинки. Но в качестве побочного эффекта позволяет серверу узнать, установлены ли у пользователя cookie (если соответствующим образом запрограммировать CGI-скрипт image.cgi на стороне сервера):

    <SCRIPT>
    function change_image(x)
    {document.x.src='http://1.ru/image.cgi?'+document.cookie}
    </SCRIPT>
    <A HREF="javascript:change_image(i);"><IMG NAME=i SRC=image1.gif></A>
    

    Эта безобидная последовательность операторов JavaScript позволит нам узнать получил ли клиент cookie. Куки могут не поддерживаться по разным причинам. В данном случае программа передает на сервер выставленные им cookie в качестве параметра скрипта под видом изменения картинки.

    Модель безопасности

    При программировании на JavaScript потенциально существует возможность доступа из программы к персональной информации пользователя. Такая проблема возникает всегда, когда нечто, запускаемое на компьютере, имеет возможность самостоятельно организовать обмен данными по сети с удаленным сервером. От версии к версии управление защитой таких данных постоянно совершенствуется, но всегда нужно иметь в виду, что множество "следопытов" исследует эту проблему и постоянно открывает все новые и новые возможности обхода механизмов защиты. Объясним только основные моменты в принципах защиты информации в JavaScript, а поиск потенциально слабых мест оставим в качестве домашнего задания для наиболее пытливых читателей.

    По умолчанию к защищенным в JavaScript данным относятся:

    Таблица 8.1. Свойства и методы, являющиеся защищенными
    Объект или классСвойства
    documentcookie, domain, lastModified, location, referrer, title, URL, links[], forms[]
    Formaction
    элемент формыchecked, defaultChecked, name, value, defaultValue, selectedIndex, toString()
    historycurrent, next, previous, toString(), все элементы массива посещенных адресов
    Location, Link, Areahash, host, hostname, href, pathname, port, protocol, search, toString()
    OptiondefaultSelected, selected, text, value
    WindowdefaultStatus, status

    Защищенными эти данные являются с той точки зрения, что программа не может получить значения соответствующих атрибутов. Главным образом речь здесь идет о программе, которая пытается получить доступ к данным, которые определены на другой странице (не на той, в рамках которой данная программа исполняется). Например, к данным из другого окна.

    В настоящее время известны три модели защиты: запрет на доступ (Navigator 2.0), taint model (Navigator 3.0), защита через Java (Navigator 4.0). Применение моделей и соответствующие приемы программирования - это отдельный сложный вопрос, требующий знаний и навыков программирования на языке Java, поэтому в рамках данного курса мы его рассматривать не будем.

    Отметим только, что к большинству свойств объектов текущего окна программист имеет доступ. Они становятся защищенными только в том случае, если относятся к документу в другом окне и загруженному из другого Web-узла. Поэтому ограничения, накладываемые системой безопасности JavaScript, достаточно гибкие и не очень сильно мешают разработке страниц с применением этого языка программирования.

  • Отвечу на любые вопросы. С уважением, Дмитрий Владимирович.

    Ваше письмо×
    Free Web Hosting