63 заметки с тегом

prog

Позднее Ctrl + ↑

Колдуем с MHTML

Позже, после того, как я уже написал эту статью, мне прислали необходимое уточнение. Я провёл несколько экспериментов, попросил своих друзей и знакомых опросить своих друзей и знакомых и вот что выяснилось:

В некоторых версиях Windows XP, 2000 (закономерность не выявлена, но, видимо, дело в наличии каких-то Service Pack) и, кажется, во всех 2003, способ не работает, если расширение файла не mht. Это несколько сужает сферу применения данного способа.

Обход этой проблемы читайте в следующей статье. Далее сама статья:

Сегодня немного поколдуем, ок? Тем, кому не интересна кухня, где готовят внутренности сайтов, могут перейти к просмотру чего-то более интересного, остальные оставайтесь.

Браузеры всё больше превращаются в комбайны, которые поддерживают десятки схем доступа к данным, от всем известных file, ftp, http, https, до полузабытых gopher, finger, news и так далее. Среди этого списка есть интересная схема — data, который поддерживают уже, кажется, все браузеры, кроме Internet Explorer (включая беты седьмых версий). Для тех, кто незнаком с ним, общий вид строки запроса в этой схеме выглядит так:

data://[mime-тип;][тип кодирования],данные...

смысл в том, что данные (картинки, javascript, другие ресурсы) можно помещать внутрь HTML. Эта схема является частью стандарта HTML 4.0, если я не ошибаюсь, но, в силу того, что она не поддерживается основным игроком на рынке браузеров, используется лишь эпизодически — чаще всего в интранет-приложениях и расширениях к Opera и Mozilla FireFox. Выглядит это, примерно, так:

<img src="data:image/png;base64,iVBORw0KGgoAAAAN...и так далее">

Эта схема, кроме возможности помещать различные данные в HTML, имеет и более очевидные преимущества. Обычно, браузер открывает не более 2-4 соединений с сервером, через которые и получает данные. Если на странице очень много мелких картинок, браузер не имеет возможности загрузить и показать их одновременно — приходится ограничиваться означенным количеством соединений.

Мы, в нашей студии, обычно стараемся использовать карту ссылок, вместо нескольких мелких кнопок, например, для ускорения загрузки. Я очень много разговаривал с поисковиками Google и Yandex, но они уверены, что в IE внедрение ресурсов в HTML невозможно. Так ли это?

Нет. По крайней мере мне удалось найти минимум один способ сделать это. В интернете я ничего подобного не встретил, так что будет считать, что его изобрёл я. Натолкнула меня на эту мысль одна из кошмарных домашних страниц, которую сверстали в Microsoft Word и сохранили... в формате MHTML.

Этот формат первым начали использовать в браузере от Microsoft (поэтому его иногда расшифровывают как «Microsoft HTML») для того, чтобы можно было сохранять текст со всем содержимым — стилями, изображениями в одном файле, поэтому этот формат ещё называют веб-архивом (Web Archive). Чаще же всего его название расшифровывают как MIME HTML и в этом заключается главная проблема.

MIME HTML сильно отличается от обычного HTML форматом — внутри заголовок, информация об именах и форматах файлов, кодированные файлы изображений и так далее — более всего это похоже на файлы писем с вложениями (посмотрите исходный текст письма в своём почтовом клиенте). Нам было бы интересней, если бы в остальных браузерах показывалось бы что-то более похожее на HTML.

Просматривая веб-архив в браузере, я обнаружил, что IE отображает файлы архива через специальную схему — mhtml. У него есть интересная особенность — он позволяет адресовать файлы внутри веб-архива извне, вот так:

mhtml:http://example.com/file.mht!image.jpg

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

<!--
Content-Type: multipart/related; boundary="=_NextPart_01C6A9B1.539AB070"

--=_NextPart_01C6A9B1.539AB070
Content-Location: index.htm
Content-Transfer-Encoding: base64
Content-Type: text/html

--=_NextPart_01C6A9B1.539AB070
Content-Location: img.jpg
Content-Transfer-Encoding: base64
Content-Type: image/jpeg

4AAQSkZJRgA....закодированная...base64...картинка...BAgAAZABkAADMdRZ

--=_NextPart_01C6A9B1.539AB070-->

<body>
<img src="mhtml:http://www.test/index.html!img.jpg">
</body>

Те, кто знает как устроено обычное электронное письмо изнутри, разберутся и сами, для остальных расскажу. Строка, которую вы видите после слова «boundary», разделяет файл на секции. Секции начинаются с двух минусов и разделяющей строки. Заканчивается веб-архив разделяющей строкой, которая начинается и заканчивается двумя минусами — у меня завершающие минусы являются частью закрывающей части HTML-коментария. У секций есть заголовок:

Content-Location — имя файла внутри архива;
Content-Tranfer-Encoding — формат кодирования файла, экономичнее всего использовать base64;
Content-Type — MIME-тип файла;

если вы не знаете, что такое base64 и MIME-тип, то лучше обратиться к поисковику — такие вещи лучше знать. Первая секция с типом «text/html» — обязательная, без неё Internet Explorer не отобразит ваши файлы, остальные могут содержать данные любых типов. Ссылка может указывать на тот же самый или любой другой файл, который содержит веб-архив.

Я не уверен насчёт четвёртой версии браузера, но 5.0 и выше должны отобразить такой HTML нормально. У этого способа есть преимущество перед схемой data — на файл можно ссылаться несколько раз. Организация вывода разного контента для двух групп браузеров — сейчас довольно простая задача. Лучше всего, конечно, использовать для этого язык на стороне сервера (для JavaScript объёмы данных слишком значительные) — например, SSI или PHP.

Кстати, Opera 9 так же поддерживает веб-архивы, но использует для этой цели другую схему — «attachment». Но Opera 9 слишком мало распространена и, кроме того, этот браузер давно поддерживает схему «data».

Ещё хотелось бы упомянуть, что Internet Explorer поддерживает ещё одну схему, через которую, вероятно, можно получить доступ к ресурсам веб-архива — «mid», но я в этом не уверен и никаких экспериментов по этому поводу не проводил.

Загадки PHP

Да что это я всё о себе, да о себе? Давайте я вам лучше о PHP расскажу! PHP, несмотря на кажущуюся простоту, язык уникальный своими загадками. Возникают, они, в основном из-за того, что какие-то вещи делались в PHP хаками, чаще — как бог на душу положит. Например, ссылки.

Ссылки в PHP — совершенно особая сущность. Небольшой ликбез. Ссылка — это способ «соединить» две переменные так, что меняя значение одной, меняется значение второй и наоборот. Записывается ссылка очень просто: $foo = &$bar. Кажущаяся простота ссылок обманчива. Я сейчас буду рассматривать PHP5, поскольку мне кажется, что четвёртая версия должна умереть как можно скорее. Все примеры были проверены под PHP 5.1.4 и PHP 5.2.0RC1 под Windows и Linux.

<?php
    $a = new stdClass();
    function Bar($bar)
    {
        $bar->val = 1;
    }
    Bar($a);
    print_r($a);

Проблема номер раз. Как вы думаете, что напечатает print_r? Отвечу сразу: он покажет свойство «val» со значением «один». Почему так произошло? Дело в том, что в PHP5 (как и во многих других языках) все объекты передаются по ссылке. Это значит, что переменная $bar в функции ссылается на переменную $a, которую мы указали в качестве параметра и все изменения $bar коснутся переменной $a. Если мы вызовем функцию «Bar» с другим параметром, то $bar будет ссылаться на этот новый параметр.

Ситуация, когда объект в функцию нужно передать по значению, на практике — почти невероятна, впрочем, если она и возникнет, объект всегда можно клонировать — в PHP5 есть специальные конструкции.

<?php
    $v = array(1);
    $b = &$v[0];
    $c = $v;
    $v[0] = 100;

    echo $c[0];

Проблема номер два. Что же будет внутри $c[0]? Странно, но — 100. Как же так? Всё просто. В PHP, если мы создали ссылку на переменную, то оба имени переменных становятся ссылками. Получается, что присваивая $c внутренность $v, я его присваиваю вместе с $v[0], которая стала ссылкой.

Странно? Совсем нет. Дело в том, что имя переменной — это тоже ссылка. Ссылка на область памяти, где записано её значение. Если ссылок на место, занимаемое переменной, больше чем одна, то все её имена ведут себя как «ссылка», если же удалить «лишние», то последнее имя теряет свою «ссылочность». Пример:

<?php
    $v = array(1);
    $b = &$v[0];

    var_dump($v);

    unset($b);

    var_dump($v);

Первый «var_dump» покажет нам ссылку, второй — нет.

<?php
    $bar = 1;

    function Foo()
    {
        global $bar;
        $test = 100;

        $bar = &$test;
    }

    Foo();
    echo $bar;

Проблема номер три. Кстати говоря, когда вы используете конструкцию «global», на деле вы используете ссылку. Дело в том, что «global $bar» полностью эквивалентна «$bar =& $GLOBALS[’bar’]». Если вы не знаете что такое $GLOBALS, самое время открыть руководство по PHP и узнать. Так вот. Проблема заключается в том, что «магическое» слово «global» прячет от нас всю эту операцию, поэтому кажется, что в глобальной переменной $bar должно быть значение 100. Это не так.

На деле, «$bar = &$test» вызовет переназначение ссылки. То есть, прежняя ссылка «$bar — это $GLOBALS[’bar’]» пропадёт и на её место встанет «$bar — это $test». Значение $GLOBALS[’bar’], естественно, не изменится.

Ну а теперь, вооружённые этими новыми знаниями, мы рассмотрим следующий интересный пример.

<?php
    $v = array(1);
    foreach ($v as &$item) echo $item;

    function Bar($foo)
    {
        $foo[0] = 100;
    }

    Bar($v);
    var_dump($v);

Итак! Что выведет «var_dump»? Правильно — 100. Почему? Вполне логично — в цикле мы проходим по элементам массива, причём $item ссылается на текущий элемент как ссылка. После цикла остаётся ссылка «$item — это $v[0]», поэтому, когда я передал в «Bar» переменную $v, её нулевой элемент остался ссылкой. Остальное понять труда не составляет. Давайте пойдём дальше.

<?php
    $v = array(1, 2);
    foreach ($v as &$item) echo $item;

    function Bar($foo)
    {
        $foo[0] = 100;
        $foo[1] = 100;
    }

    Bar($v);
    var_dump($v);

Что же тут покажет «var_dump»? Странно, но в обоих элементах массива будет... 100. Казалось бы — bug PHP (впрочем, я не настаиваю — может это и баг). Оказывается, не совсем. Дело в том, что ссылка в PHP уничтожается конструкцией unset и только ей. При переприсвоении ссылки прежняя ссылка не удаляется, точнее, PHP не трогает счётчик ссылок. В моём следующем примере все три переменные будут ссылкой (в чём легко убедиться, сделав «var_dump переменной $GLOBALS»):

<?php
    $v = 1;
    $k = 2;
    $b = &$v;
    $b = &$k;

Первый же unset «восстановит справедливость»:

<?php
    $v = 1;
    $b = &$v;
    $t = &$v;

    $k = 2;
    $b = &$k;

    unset($t);

Теперь переменные $k и $b — ссылочные, а $v — нет. Видимо, «пересчёт» ссылок при пересвоении вызывает какие-то трудности. Хотя эта особенность PHP вызывает целую кучу трудноуловимых багов. Спасибо моему братишке — Олегу Степанищеву за то, что обратил мой внимание на пример с foreach, после чего я и решил структурировать свои знания по этому поводу.

Панель на картинке

Ещё одно изобретение Microsoft — панель, которая появляется при наведении на картинку. Функция хорошая, но на элементах оформления эта панель здорово портит кровь, особенно в тех случаях, когда нужно отобразить диалог, заблокировав и затемнив остальное содержимое сайта полупрозрачной картинкой — такой метод изготовления модальных диалогов всё чаще используется в вебе.

Вот в этих случаях не нужно, чтобы при наведении на картинку пользователь видел эту панель. Есть два способа избавиться от неё. Во-первых, можно запретить её появление на всей странице при помощи мета-тега: <meta http-equiv=«imagetoolbar» content=«no» />

Во-вторых, можно указать для какой картинки её показывать не нужно, используя нестандартный атрибут galleryimg

<img src="/img/image.jpg" width="100" height="100" alt="" galleryimg="no" >

Explorer Canvas

IE не обновлялся очень давно и не поддерживает тег CANVAS, предоставляющий интересные возможности — рисование через JavaScript вектором. Интересно, что Microsoft когда-то давно предлагала к использованию свою разработку — язык VML, а ранее браузеры позволяли использовать VRML. Последний благополучно умер, а VML так и поддерживается современными версиями Internet Explorer.

Вероятно некоторая схожесть технологий VML и CANVAS вдохновила Emil A Eklund на создание Explorer Canvas — трансляцию вызовов CANVAS в теги VML. Поддержка пока ограниченная, но ведётся активная работа по расширению возможностей. На сайте есть небольшая демонстрация возможностей. Кстати, хорошо видно, что в браузерах Opera и FireFox canvas ведёт себя по-разному.

IE и HTTPS

Следующая проблема — некорректное поведение IE при использовании протокола HTTPS. Иногда (особенно на сайтах, где используются технологии изменения содержимого страницы без её перезагрузки), в странице используется тег iframe без содержимого или с указанием about:blank. IE считает, что такой IFRAME загружает содержимое не по протоколу HTTPS (формально он прав — конечно не загружает, но по сути-то содержимое пустое) и пишет предупреждение пользователю.

От этого раздражающего предупреждения можно избавиться следующим образом: указать в качестве src для iframe конструкцию

javascript:''

Т. е.

<iframe src="javascript:''></iframe>

javascript — это протокол, который позволяет выполнять JavaScript. Когда страница грузится по HTTPS этот скрипт выполняется в текущем протоколе — HTTPS, а указание пустой строки позволяет создать фрейм именно с пустым содержимым.

Выполнение JS только в IE

Кстати говоря, есть два способа выполнения JavaScript-кода только в IE. Первый, который, правда, сработает и в Opera — указание в script language «JScript». JScript — это название диалекта JavaScript, реализованного в IE. Следующее:

<script language="JScript">alert('1')</script>

cработает только в Opera и Internet Explorer.

Второй способ — так называемая «условная компиляция». Был придуман Microsoft, чтобы прятать новые конструкции языка (например, try ... catch) от предыдущих версий браузера. Следующий код выполнится только в Internet Explorer. Остальные браузеры посчитают его комментарием в коде JavaScript

/*@cc_on

alert(1)

@*/

В «условной компиляции» можно выполнять ещё ряд действий, например, проверять версию jscript и т.д, всё это легко найти в любом поисковике.

Условные комментарии

Одна из проблем — как подключать какие-то части кода в зависимости от того является ли браузер Internet Explorer. Веб-мастера, обычно, пользуются CSS-хаками, а вот о том, что есть специальная конструкция, которая позволяется это сделать, мало кто знает.

Специальная конструкция

<--[if IE...] ... <![endif]-->

позволяет не только прятать какие-то части HTML от других браузеров (которые воспринимают их как обычный HTML-комментарий), но и выбирать HTML в зависимости от версии IE. Более подробно можно прочитать, например, в статье Manfred Staudinger Taming Your Multiple IE Standalones. Для тех, кто не знает английского, скажу — общий принцип легко понятен из примеров.

Кстати, от Internet Explorer так же можно прятать HTML-код — надо просто заключить его в теги

<comment> … </comment>

Этот нестандартный тег Internet Explorer считает комментарием и, соответственно, не показывает и не обрабатывает его содержимое.

Борьба с Activating ActiveX Controls

Одно из новшевств, которое появилось в последних обновлениях Internet Explorer — это «Activating ActiveX Controls» (KB912945). После обновления Internet Explorer позволяет управлять элементами, которые находятся на ActiveX (а к ним относится и Flash) только после клика на объект, сам объект до клика выделяется рамкой, если над ним провести мышкой. Само по себе это не так уж и мешает, только если этот ActiveX (Flash, например) не находится за каким-то контентом и если на нём находятся ссылки, то кликнуть по ним получается только со второго раза — первым активизируется ActiveX и только потом срабатывает ссылка.

Оказывается есть метод борьбы с этой проблемой. Всё что нужно сделать — обойти все теги object и присвоить их outerHTML самому себе. Например, так:

// When the page loads:
window.onload = function(){
  if (document.getElementsByTagName) {
    // Get all the tags of type object in the page.
      var objs = document.getElementsByTagName("object");
      for (i=0; i<objs.length; i++) {
        // Get the HTML content of each object tag
        // and replace it with itself.
        objs[i].outerHTML = objs[i].outerHTML;
      }
   }
}
// When the page unloads:
window.onunload = function() {
  if (document.getElementsByTagName) {
    //Get all the tags of type object in the page.
    var objs = document.getElementsByTagName("object");
    for (i=0; i<objs.length; i++) {
      // Clear out the HTML content of each object tag
      // to prevent an IE memory leak issue.
      objs[i].outerHTML = "";
    }
  }
}

PHP для PocketPC

Одной из особенностей языка PHP является то, что существует ровно один интерпретатор этого языка. Это хорошо — никакого разночтения стандартов и прочих проблем наличия интерпретаторов от разных команд. Или, по крайней мере, другие мне не попадались. До сих пор.

Некие умельцы написали Pocket HPH — интерпретатор для PocketPC. Конечно, реализованы далеко не все функции, но и того набора, что есть, хватит, чтобы реализовать что-то приличное. Например, есть даже функции работы с SQLite и регулярными выражениями!

XSS

Одна из известных проблем безопасности на веб-сайта — так называемый XSS (Cross Site Scripting). Вкратце суть в том, что иногда удаётся внедрить на страницу некий код (на скриптовом языке), который может выполнить некоторые вредоностные действия. Например, украсть файлы cookie для этого сайта, если таковые имеются. В cookie часто в каком-то виде хранится информация, которая позволит хакеру залогиниться на этот сайт под вашим логином.

Похоже, фирма Microsoft первая, кто озаботился поиском принципиально новых возможностей решения этой проблемы. Microsoft добавила в свой браузер поддержку так называемых «HTTP-only cookie». Смысл в том, чтобы ставить cookie специальный флаг, который будет показывать, что данную cookie браузер должен сделать недоступной для скриптовых языков. Пример:

Set-Cookie: USER=123; expires=Wednesday, 09-Nov-99 23:12:40 GMT; HttpOnly

Предельно понятно и достаточно действенно. Пока в PHP, в setcookie нет поддержки этого флага, такую cookie можно ставить через вызов header.

Ранее Ctrl + ↓