php

PHP2EXE

Решил посмотреть что у нас на старую добрую тему php2exe — т. е. PHP-компиляторов. В последнее время в разговорах всё чаще мелькало упоминание RoadSend PHP Compiler, вот я его решил скачать и скомпилировать свой «Photoshop’s Crap Remover». Эта програмка была написана в 2002-м году для удаления из файлов JPEG лишней информации. Фотошоп тогда очень активно её туда записывал — вплоть до preview, так что было актуально, но и сейчас, бывает, программа пригождается.

Вот этот компилятор я и скачал. Версия 2.9.0, бета. Первое, что я выяснил, когда нашёл где включается режим вывода лога компиляции, инсталлировать надо было туда, куда программа предлагала по умолчанию. Путь «Program Files» вызывает проблемы из-за пробела посередине. Следующая проблема — библиотека для работы с регулярными выражениями в формате Perl породила массу ошибок. Я решил это легко — у меня в программе одно, незначащее регулярное выражение, т. е. я его просто убрал.

В итоге, эксперимент окончился плачевно. Мало того, что итоговый файл оказался размером шесть мегабайт (при том, что исходная программа занимает 1.5 килобайта) — это ещё терпимо, понятно, что с ростом исходной программы этот размер меняться почти не будет, да и упаковывается он до одного мегабайта, например, при помощи NPack. Хуже, что скомпилированная (даже не сжатая) программа так и не заработала — при выполнении возникают ошибки переполнения.

Вывод: возможно, это огрехи бета-версии, но инструмент нерабочий.

8 комментариев

Генерация «трафки™» или распределение по весам

Дима Смирнов применил интересное решение для генерации «трафок» на «Блоговаре»:

$rb   = str_pad (decbin (mt_rand (0,64)),6,"0", STR_PAD_LEFT);
$herb = (int) strpos ($rb, "1");

Смысл в том, что каждое следующее значение должно выпадать с вдвое меньшей вероятностью, чем предыдущее. Пока я спал, мне пришло в голову довольно забавное решение на ту же тему. Оно быстрее примерно на 30%, не обладает ограничением на размер и точнее вычисляет вероятности (у Димы «трафка» не выпадает с вероятностью примерно 52%), но не решает задачу полностью. Кто скажет в чём разница, не запуская код?

for ($i = 5; $i && mt_rand(0, 1); --$i);

10 комментариев

Сайт на мобильных устройствах: Wall4PHP

Латвийский парень Kaspars Foigts реализовал библиотеку Wall4PHP. Это реализация WALL (Wireless Abstraction Library, оригинальная версия создана Luca Passani на Java) на языке PHP. Библиотека предназначена для поддержки сайтом мобильных устройств и позволяет на специальном языке описывать вёрстку, чтобы потом показывать сайт на том языке, который поддерживается устройством (XHTML, WML, модифицированный HTML для Imode-устройств).

Профиль устройства (что именно им поддерживается) описывается на специальном XML-языке WURFL, по этим профилям WALL определяет в каком виде устройству показать сайт. На странице автора есть англоязычное описание возможностей библиотеки, языка, а так же несколько примеров работы. Библиотека не развивается с конца 2006-го года (хотя на сайте есть TODO), но, похоже, достаточно стабильна и функциональна.

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

Разделяемая память

Удивительная вещь — до появления memcached никто, как будто бы, и не знал о существовании разделяемой памяти. Сейчас memcached почему-то называют лучшим решением. Это не так — у этого решения есть особенности, которые в некоторых проектах делают его не лучшим выбором. Memcached хорош двумя вещами — он позволяет держать данные в едином хранилище (например, несколько frontend делят общий кеш) и объединять несколько серверов (масштабируемость). Другие его особенности делают его выбор в некоторых проектах нерациональным (в других — напротив, лучшим решением), а именно: отсутствие блокировок (нельзя заблокировать какой-то ключ, прочитать, вычислить новое значение, разблокировать) и работа через TCP/IP.

Кроме того, у memcached есть особенность, которую тоже надо учитывать. Есть случаи, когда memcached может удалить данные из кеша, вот комментарий разработчика Анатолия Воробья:

Есть набор классов по величине памяти — например, в самом простом и дефолтном варианте, есть класс размером в 128 байт, потом 256 байт, потом 512, 1k, 2k итд. до 1Mb. Если какая-то запись (ключ+значение+ дополнительные заголовки) занимает, скажем, 1.5k, под нее отводится кусок из класса в 2k, т. е. кусок размером в 2k, и она в него пишется.

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

Ссылки на другие способы доступа к разделяемой памяти есть в аннотации к модулю PEAR System::SharedMemory.

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

Скорость замены в PHP

Провёл небольшие замеры в PHP. Задача следующая — заменить вхождения между двумя ограничителями (в моём случае — фигурные скобки) по хешу, где ключём хеша является значение, которое заменяем, значением хеша — то, на что заменяем. Сделал несколько реализаций, с использованием различный функций (слева функция, справа — время в секундах замены из одного и того же словаря на приличного размера тексте):

strpos — 0.23691987991333
explode — 0.27920699119568
preg_replace_callback — 0.34914493560791
str_replace — 1.8960340023041
strtr — 3.1935269832611

Во многом результаты довольно ожидаемые, но кое-что удивило — скорость замены через str_replace и strtr сильно различается, а explode (разбиение по первому ограничителю, потом, что осталось — по второму) почему-то оказался почти таким же быстрым, как и strpos (поиск первого ограничителя, потом второго).

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

Загадки 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, после чего я и решил структурировать свои знания по этому поводу.

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

PHP для PocketPC

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

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

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

Библиотека для работы с XBM и моно-BMP

Когда-то я делал библиотеку PEAR для графических форматов XBM и BMP (монохромного). Хотелось получить средство вывода графики в том случае, если на хостинге нет библиотеки GD, чтобы, хотя бы, какие-то простенькие диаграммы можно было строить. BMP — персонально для IE, в котором убрали поддержку формата BMP из-за ошибки в библиотеке отображения этого формата. Парням из Microsoft показалось, что проще ликвидировать поддержку, чем исправлять ошибку.

Одним словом, какое-то время назад у меня опять зачесались руки осчастливить человечество библиотекой, поддерживающей какой-нибудь умирающий формат.

Когда-то, когда сотовые телефоны ещё были большими, была такая штука — WAP, этакий урезанный WWW для сотовых телефонов. Собственно, WAP и сейчас пока живее всех живых, хотя сайтов в этом формате относительно немного. В WAP использовались картинки в специальном формате — WBMP (Wireless BMP, level 0). Если кому-нибудь ещё интересен этот формат — добро пожаловать, у меня теперь есть библиотека для работы с ним.

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

Разделяемая память и PHP

В основном на PHP пишут небольшие проекты, поэтому литература кишит примерами «как сделать сайт в три странички». Но, тем не менее, только простыми сайтами дело не ограничивается. Когда приходится делать производительные проекты, существует множество ухищрений и решений, одно из которых — использовать разделяемую память, там где кешировать данные в статические файлы не получается. Различных API для использования разделяемой памяти много, только в одном PHP есть shmop, shm_* и Memcache, что несколько осложняет дело.

Я сделал PEAR-класс System::SharedMemory для работы с разными видами разделяемой памяти, получить его можно у меня или на сайте PEAR.

В настоящий момент поддерживаются следующие API: 

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

Brainfuck

Как-то очень давно я наткнулся в сети на язык с названием Brainfuck. Его идея, так подходящая к его названию, так меня захватила, что я в своё время создал оптимизирующий компилятор программ Brainfuck в JavaScript, выполненный в виде минишелла и работающий прямо в браузере. Язык интересен тем, что содержит всего восемь конструкций, но позволяет писать программу любой сложности, правда скорость выполнения…

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

Модуль к PHP написан в формате PEAR, т. е. его можно либо использовать непосредственно, либо исталлировать в составе PEAR.

Нельзя сказать, что я компилятор проводит глубокую оптимизацию, но, например, код «[->+>++<<][>+>[-]<<-][>+<[-]]" будет преобразован в

<?php
$in = array(0, $id = 0);
$d = array_fill(-65535, 65535, $di = 0);

$d[$di+1]+= $d[$di];
$d[$di+2]+= $d[$di]*2;
$d[$di] = 0;

$d[$di+1]+= $d[$di];
if ($d[$di]) $d[$di+2] = 0;
$d[$di] = 0;

$d[$di+1]+= $d[$di];
$d[$di] = 0;

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

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

Блоки и локальные переменные в PHP

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

Рассуждал я как-то — чего же мне не хватает в PHP — языке, которому я посвящаю почти всё своё рабочее время, плюс немного свободного? Помимо всяких мелочей, жизнь без которых не так мила, но, в общем-то, вполне сносна. Не хватает мне вещи, которую я впервые увидел в Perl’e — языке, с такой близкой мне идеологией — который дарит программисту то, что он сам бы никогда не купил.

Не хватает мне возможности изоляции переменных. Чтобы можно было, не задумываясь нет ли вверху чего-то с похожим именем, написать вот такое:

<?php
{
   var $i;
   echo $i = 1;
}
echo $i;

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

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

Вторая причина — у вас никогда такого не было, что вы перетёрли переменную, которая используется где-то ниже? Например, вы правите чужой код и не в курсе, что переменная в вашей вставке, которой вы дали, на ваш взгляд, уникальное имя $dlja_tmp_2 уже используется и хранит что-то необычайно важное. Используя блок изоляции, можно быть уверенным, что ничего подобного не произойдёт.

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

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

Формат графики XBM

Есть такой интересный принцип дарить подарки — покупать в подарок то, что люди сами бы себе никогда не купили. У меня, похоже, одно из хобби — копать там, где ничего не растёт. В том смысле, что меня больше увлекают проекты, где практической пользы меньше, чем льда на Солнце.

Было время, меня увлёк графический формат XBM, единственное достоинство которого (впрочем, вполне достаточное, на мой взгляд) — безумная простота, позволяющая манипулировать графикой в этом формате из любого хоть чего-то стоящего языка программирования. Я даже разродился модулем ImageXBM и прочими вещами.

И вот я узнаю, что Internet Explorer 6.0SP1 и выше не поддерживает мой любимый формат! Причину цитирую:

A  change was made to Internet Explorer for Windows XP Service Pack 2 and Windows Server 2003 Service Pack 1 so that it does not render the XBM image file format. This update adds this change to all other supported versions of Internet Explorer. This change has been made to help prevent the Internet Explorer process from closing unexpectedly when users view invalidly-formed images. The XBM format is no longer supported by Internet Explorer.

Что переводится, примерно, так: «у нас что-то не работало, поэтому мы просто это отключили». Оплакал я смерть формата, которые за годы своей жизни так и не стал известен большинству разработчиков, да и сделал другой модуль.

Image_MonoBMP — модуля для работы с монохромными изображениями в формате BMP. А чтобы подтвердить, что я опять дарю вам то, что вы себе сами бы не купили, скажу, что этот формат, конечно, поддерживается Internet Explorer и Opera, но FireFox плевать на него хотел, да, в общем-то, и правильно.

Кстати, есть у меня идея сделать поддержку WBMP, но как же быть с принципом? Впрочем, я его уже нарушил, но об этом как-нибудь в другой раз.

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

Sort в PHP

Оказывается функция sort в PHP не использует установки локали в PHP4. Косвенно это можно узнать из руководства:

Sorting type flags:

SORT_REGULAR — compare items normally

SORT_NUMERIC — compare items numerically

SORT_STRING — compare items as strings

SORT_LOCALE_STRING — compare items as strings, based on the current locale. Added in PHP 5.0.2

Параметр SORT_LOCALE_STRING появился только в PHP 5.0.2. На мой взгляд, это очень странно. Valshooter рекомендует в этом случае использовать

usort($fruit, 'strcoll');

правда, в том случае, если локаль у вас UTF8 вряд ли сортировка вернёт осмысленные данные.

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

myPHPinfo

Известно, что некоторые хостеры отключают функцию phpinfo из соображений безопасности. Неприятно, но часто приходится мириться и делать какие-то проверочные скрипты, чтобы узнать что же всё-таки есть на сервере.

Именно для таких случаев я написал myPHPinfo — программу на PHP, эмулирующую вывод функции phpinfo, насколько это возможно, конечно. Инструкции, ссылки для скачивания и изучения примеров работы есть в специальном разделе сайта.

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

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

Новый 8sender

Обновился 8sender (PHP) — программа, позволяющая отправлять на e-mail файлы в формате «BOLK quoted printable» (это наиболее экономичный формат пересылки файлов по почте). Программа писалась с единственной целью — экономить трафик и время адресата.

Новое в этой версии:

  • Имена передаваемых файлов записываются ещё и в тему письма.
  • E-mail отправителя теперь можно не указывать — в этом случае
    система автоматически подставит «admin@example.com»

Напоминаю, что для того, чтобы использовать эту программу, вам нужен интерпретатор языка PHP (минимально — файлы php.exe и php4ts.dll).

Для удобства тех, кто работает под Windows я сделал специальный файл — 8sender.bat. Скачивать и запускать нужно его, в пределах видимости скрипта должен быть расположен файл php.exe — той же папке или в любой папке, указанной в системной переменной PATH.

Плохие новости владельцев Windows 95/98/ME — вам придётся указать SMTP сервер для отправки сообщений. Инструкцию по запуску можно получить, запустив программу без параметров.

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

Сессии в SSI

Один из проектов, в разработке которого я участвовал в недавнем времени, расчитывался на огромную посещаемость (забегая вперёд, скажу — проект действительно стал исключительно посещаемым), в связи с чем пришлось разрабатывать и использовать технические решения, позволяющие проекту не «падать» под огромными нагрузками.

В частности, была разработана система кеширования, которую можно охарактерировать как «всё по максимуму в в HTML, остальное — включаем через SSI и фреймы». Поскольку на сайте используются пользовательские сессии, сложилась ситуация, когда сессию должны могли инициировать и скрипты (язык программирования — PHP, основной сервер — Apache 1.x), включенные через SSI. Тут надо заметить, что из-за жёсткого требования заказчика — в строке запроса не должны присутствовать PHPSID и т.п (на сайте используются ЧПУ), использование trans_sid исключалось.

Trans_sid использовать нельзя, скрипты, включенные через SSI заголовок выдать не могут и, следовательно, проставить сессию в cookie — тоже. Сначала я, было, решил полностью отказаться от SSI и работать исключительно с iframe, но подумал — нельзя ли Apache заставить генерировать идентификатор сессии самостоятельно?

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

This module provides a magic token for each request which is guaranteed to be unique across «all» requests under very specific conditions. The unique identifier is even unique across multiple machines in a properly configured cluster of machines.

Внимательно пролистав список официальных модулей Apache, я нашёл в списке mod_usertrack — модуль, маркирующий пользователей при помощи cookie. Обычно он используется для того, чтобы различать пользователей в логах веб-сервера, но для нас главно то, что он маркирует пользователей идентификатором, который можно прочитать.

Проведя испытания, я столкнулся с вполне ожидаемыми трудностями — во время первого запроса, когда сервер только что отослал cookie клиенту, в переменной PHP \_COOKIE$ пусто — cookie ещё не пришли от клиента назад и, соотвественно, не были записаны в эту переменную.

В PHP есть функция apache_response_headers, возвращающая массив всех заголовков, выдаваемых в HTTP-запросе в данный момент. Естественно, в числе этих заголовков нашёлся и тот, что отвечает за простановку cookie. Функция, читающая такой заголовок и cookie, может выглядеть, например, так:

<?php
function U_sid()
{
    // Если идентификатор уже есть в cookie, возвращаем его
    if (isset($_COOKIE['Apache'])) return $_COOKIE['Apache'];

    // Иначе - ищем его в ответе Apache
    $responce = apache_response_headers();
    foreach (preg_split('/s*;s*/', @$responce['Set-Cookie']) as $chunk)
    {
        list($key, $value) = explode('=', $chunk, 2);
        if ($key == 'Apache') return $value;
    }

    return null;
}

«Apache» — это имя cookie, которое проставляет Apache. Далее — всё просто, в php.ini отключаем «session.use_trans_sid» и «session.use_cookies», чтобы запретить PHP создавать сессии, в .htaccess помещаем директивы модуля mod_usertrack:

CookieFormat Compact CookieTracking on CookieStyle Cookie

А в библиотечный файл (или наши файлы PHP, поближе к началу), вызов необходимых, для создания сессии, функций:

<?php
// Узнаём идентификатор сессии
if (($sid = U_sid()) !== null)
{
    // Если он не null, создаём сессию
    session_id($sid);
    session_start();
}

В итоге мы получаем cookie-сессию, которая создаётся даже в файле, включенном через SSI.

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

8sender

Написал программу (2.65Кб), позволяющую отправлять вложения в формате bolk quoted printable. На PHP. Вкратце напомню — это формат, который, в отличие от остальных форматов вложений (quoted printable, UUE, Base64), минимально увеличивает размер файла. Основные почтовые клиенты его понимают.

Утилита command-line, на вход нужно подать следующие параметры: адрес отправителя, адрес получателя и имена файлов вложений (их может быть несколько). Например, так:

php 8sender.php mail@example.com bolk@example.net big.tiff

Опционально первым параметром можно указать адрес вашего SMTP, он должен быть первым после ключа -t. Если его не указать, программа попытается самостоятельно определить MX получателя. Для работы под Windows желателен PHP 4.3.0 и выше.

php 8sender.php -t smtp.example.com mail@example.com bolk@example.net big.tiff

После запуска программа покажет диалог с SMTP-сервером, не пугайтесь — так и должно быть. Если что-то не так, то на экране появится слово «Error» с кратким указанием ошибки. У программы есть один недостаток — при кодировании файла используется файловый буфер, вместо выдачи файла сразу в сокет. Мне так удобнее было отлаживать и, обычно, это не мешает, если вы не собираетесь отсылать по почте фильмы.

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

Устранение потерь при кодировании почтовых аттачей в base64

Как известно, когда-то электронные письма предназначались исключительно для обмена текстом. Соотвественно и все протоколы тогда разрабатывались в расчёте на это. Когда появилась потребность передавать файлы, поступили очень просто — файлы специальным образом преобразовывались в текст и этот текст включался в тело письма. Конечно, такое преобразование не проходило даром — объём файла сильно увеличивался от этого пребразования, поскольку «текст» — это куда меньшее количество символов, чем бинарные данные, да и речь в те времена шла только об английском тексте. Со временем, это вошло в стандарт и почтовые программы научились самостоятельно вставлять в текст файлы, преобразуя их различными методами.

Прошло время, текст в электронных письмах давно уже не только английский, а стандарты кодирования текстовых файлов до сих пор используют только латинские буквы (плюс цифры и некоторые символы, такие как плюс, миную, слеш и так далее). Мне пришло в голову — а что если попробовать кодировать файлы всем полем допустимых символов. Сейчас, когда появились письма на UTF-8, допустимыми символами являются все, за исключением символов с кодами 0x00 (из-за того, что почти всё ПО написано на C/C++, где этот символ является признаком конца строки), 0x0A и 0x0D (это символы перевода строки, не хотелось бы, чтобы какой-нибудь умный клиент в бинарном файле добавил к 0x0D символ 0x0A — по стандарту в письмах перевод строки обозначается парой этих символов и, обычно, если встречается только один из них, почтовые клиенты или MTA добавляют и второй).

Из всех вариантов кодирования, а их три (Base64, UUencode и quoted-printable) для моих целей подошёл только quoted-printable. Идея его очень проста — все недопустимые символы (и «равно») преобразуются в последовательность из трёх символов: символ «равно» и шестнадцатиричный код символа. Поскольку «допустимых» символов традиционно очень мало, то метод не слишком экономичен.

Я же задался целью проверить, что же будет, если попробовать передать файл, где поле допустимых символов расширено до максимума. Сразу скажу — эксперимент удался. Можено эксперементально внедрять новый формат кодирования писем. Назовём его Bolk Quoted-Printable. Ладно, ладно, будем скромнее. 8bit Quoted-Printable.

Я написал небольшую программу на PHP, которая в качестве параметра использует имя файла, который нужно закодировать, а на выход выдаёт этот файл в формате понятном почтовому клиенту. Таким образом, если её запустить как «php -q 8qp.php somegif.gif > somename.eml» и открыть получившийся файл вашим почтовым клиентом, внутри вы должны увидеть свою картинку.

<?php
    // Example 1. 8qp.php
    // Written by Evgeny Stepanischev aka BOLK
    $bound = 'BOLKNOTE.RU';

    $mime = getimagesize($argv[1]);
    $mime = $mime['mime'];

    $str = file_get_contents($argv[1]);
    $len = filesize($argv[1]);

    echo <<<HEADER
Content-Type: multipart/mixed;
        boundary="----{$bound}"

------{$bound}
Content-Type: $mime; name="{$argv[1]}"
Content-Disposition: attachment; filename="{$argv[1]}"
Content-Transfer-Encoding: quoted-printable


HEADER;

    $np = array (0, 10, 13, 61);

    for ($i = 0; $i<$len; $i++)
    {
        $code = ord($str{$i});

        echo in_array($code, $np) ?
        '='.strtoupper(str_pad(dechex($code), 2, '0', STR_PAD_LEFT)):
        $str{$i};
    }

    echo "rn------{$bound}--";

Для эксперимента я выбрал наугад две картинки. JPEG и GIF. И вот что получилось в итоге. «TheBAT!» — это наименование моего почтового клиента.

Случайный GIF: исходный размер — 33590, мой quoted-printable — 36300, base64 encode — 46034, TheBAT! UUencode — 47052, TheBAT! Quoted-printable — 80576.

Случайный JPEG: исходный размер — 28144, мой quoted-printable — 29656, base64 encode — 38570, TheBAT! UUencode — 39429, TheBAT! Quoted-printable — 65663.

В случае QP-кодирования моим методом, размер файла, как видно по результатам двух экспериментов, увеличился совсем ненамного — 4-8%. Если для теста взять текстовый файл, то прирост будет едва заметен, если вообще будет, надо только переводы строки для текстовых файлов не кодировать, а пускать как есть.

Эксперименты на первом же попавшемся Sendmail показали, что, во-первых, типичный MX стремиться разбить строку по размеру своего буфера (у моей жертвы этот размер оказался равен приблизительно двум килобайтам) — но это легко устранимо, достаточно делать это за него и, что более неприятно, «умница» sendmail перекодирует письмо так как считает нужным — в традиционный quoted-printable.

Последняя беда поставила меня в тупик. Правда, ненадолго. Вспомнив, что большинство клиентов умеют открывать письма в формате EML я решил пойти на хитрость — прикрепить моё письмо как аттачмент другого, замаскировав моё письмо под обычный текст — Sendmail вряд ли проверяет аттачи глубже одного вхождения. А чтобы письмо максимально адекватно воспринималось почтовым клиентом, я дал ему disposition inline и убрал из него все служебные поля. Попробовал отправить — получилось. В TheBat видно только лишнее пустое поле — беда небольшая.

<?php
    // Example 2. 8qp_next.php
    // Written by Evgeny Stepanischev aka BOLK

    $bound  = '3D1E1C92aaBF00D';
    $bound2 = '3D1E1C92xxBF00D';

    $mime = getimagesize($argv[1]);
    $mime = $mime['mime'];

    $str = file_get_contents($argv[1]);
    $len = filesize($argv[1]);

    echo <<<HEADER
From: <mailbox@sample.com>
To: mailbox@sample.com
From: mailbox@sample.com
Content-Type: multipart/mixed;
 boundary="----------{$bound2}"

------------{$bound2}
Content-Type: message/rfc822; name="1.eml"; charset=utf-8
Content-Disposition: inline; name="1.eml"
Content-Transfer-Encoding: 8bit

Content-Transfer-Encoding: 8bit
Content-Type: multipart/mixed;
        boundary="----{$bound}"

------{$bound}
Content-Type: $mime; name="{$argv[1]}"; charset=utf-8
Content-Disposition: attachment; filename="{$argv[1]}"
Content-Transfer-Encoding: quoted-printable


HEADER;

    $np = array (0, 10, 13, 61);

    for ($i = 0; $i<$len; $i++)
    {
        $code = ord($str{$i});

        echo in_array($code, $np) ?
        '='.strtoupper(str_pad(dechex($code), 2, '0', STR_PAD_LEFT)):
        $str{$i};

        if ($i && ($i % 512) == 0) echo "=rn";
    }

    echo "rn------{$bound}--",
    "rn------------{$bound2}--";

Полный размер письма после такой двойной «упаковки» увеличивается незначительно. Кстати говоря, Opera 7.0 с таким письмом не справилась. Возможно, в силу старости — текущая версия этого пакета — 7.61b. Для интереса я отправил письмо на мои ящики на серверах Mail.ru и Gmail.com. Оба сервера вполне достойно справились с задачей, единственно, что Gmail не принимал письмо, пока я не указал charset как UTF-8.

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

Патч к IMP/Horde

Если при работе с webmail IMP/Horde у вас выдаются ошибки в функции imap_fetchbody, попробуйте применить мой патч. Порядок применения:

  • Разместить в папке lib, с названием, например, bolkpatch.php.
  • В файлах lib/IMP.php и view.php после блока строк require_once надо ставить строку
    require_once IMP_BASE . ’/lib/bolkpatch.php’;
  • Заменить в этих двух файлах все вызовы imap_fetchbody на imap_fetchbody2.
  • Установить модуль PEAR Mail_Mime (pear install Mail_Mime)
// IMP patch. Written by Evgeny Stepanischev
function imap_fetchbody2(&$imap_stream, $msg_number, $part_number, $options = NULL)
{
    $params = array(&$imap_stream, &$msg_number, &$part_number);
    if ($options !== NULL)
    $params[] = &$options;

    $str = @call_user_func_array('imap_fetchbody', $params);

    if ($str == '' || $str === FALSE)
    {
        array_splice($params, 2, 1);

        $mail = @call_user_func_array('imap_fetchheader', $params).
                @call_user_func_array('imap_body', $params);

        require_once 'Mail/mimeDecode.php';

        $decoder =& new Mail_mimeDecode($mail);
        $structure = (array) $decoder->decode(array('include_bodies' => true));

        return isset($structure['parts'][$part_number-1]) ?
                $structure['parts'][$part_number-1]->body :
                $structure['body'];
    }

    return $str;
}

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

← 1 7 8 9 10 11 12 →