программирование
Код для защиты GIF, PNG и JPEG (к предыдущей статье)
В коментариях к предыдущей заметке о моём предложении по защите графических форматов от внедрённого кода один из читателей попросил привести код, который показывал бы как моё предложение работает.
Для форматов GIF, JPEG и PNG я за полчаса написал следующее (язык — PHP 5.4 и выше):
<?
// //bolknote.ru август 2012
// Защищаем изображения, возращает false в случае неудачи
function ProtectPicture($inname, $outname)
{
// типы изображения, которые мы можем защитить и функции защиты
$enabled = [
IMAGETYPE_JPEG => 'Jpeg',
IMAGETYPE_PNG => 'Png',
IMAGETYPE_GIF => 'Gif',
];
if (list(,, $type) = @getimagesize($inname)) {
if (isset($enabled[$type])) {
if ($fi = @fopen($inname, 'rb')) {
flock($fi, LOCK_SH);
if ($fo = @fopen($outname, 'wb')) {
flock($fo, LOCK_EX);
// если все файлы удалось открыть, то вызываем функцию защиты
$ret = call_user_func('ProtectPicture'.$enabled[$type], $fi, $fo, filesize($inname));
fclose($fo);
fclose($fi);
if (!$ret) {
unlink($outname);
}
return $ret;
} else {
fclose($fi);
}
}
}
}
return false;
}
/* Private functions */
function ProtectPictureJpeg($fi, $fo, $controllen)
{
// заголовок JPEG
if (!@fwrite($fo, fread($fi, 2))) return false;
$protect = '<?php __halt_compiler();';
$length = pack('n', strlen($protect) + 2);
// дописываю секцию COM (comment)
if (!@fwrite($fo, "\xFF\xFE$length$protect")) return false;
// остаток файла
if (@stream_copy_to_stream($fi, $fo) != $controllen - 2) return false;
return true;
}
function ProtectPicturePng($fi, $fo, $controllen)
{
// заголовок PNG
if (!@fwrite($fo, fread($fi, 8))) return false;
// пропускаем кусок IHDR (он должен быть первым))
$len = unpack('N', $lenbin = fread($fi, 4))[1] + 8;
$ihdr = $lenbin . fread($fi, $len);
// Смотрим, не написал ли в этот кусок хакер свой код
if (($pos = strpos($ihdr, '<?')) !== false) {
// в стандарте перечислены поля, которые есть в этом куске — там должно быть 13 байт (+4 — длина)
if ($len != 17) return false;
// эта комбинация может встретиться только в первых восьми байтах заголовка — дальше поля не
// могут принимать такие большие значения
if ($pos > 11) return false;
// если разрешены короткие теги, нужно присмотреться к этому коду
// в столь ограниченном пространстве вред можно натворить только через запуск
// чего-то внешнего
if (ini_get('short_open_tag') && strpos($ihdr, '`', $pos + 2) !== false) {
return false;
}
}
if (!@fwrite($fo, $ihdr)) return false;
// кусок iTXt (text) с нашим кодом
$protect = "<?php __halt_compiler();";
$length = pack('N', strlen($protect));
$sum = pack('N', sprintf('%u', crc32($protect)));
if (!@fwrite($fo, "{$length}iTXt{$protect}{$sum}")) return false;
// остаток файла
if (@stream_copy_to_stream($fi, $fo) != $controllen - 8 - 4 - $len) return false;
return true;
}
function ProtectPictureGif($fi, $fo, $controllen)
{
$protect = "<?php __halt_compiler();";
// заголовок GIF
if (!@fwrite($fo, fread($fi, 6))) return false;
// поле Logical Screen Descriptor
$lsd = fread($fi, 7);
$flag = ord($lsd[4]);
// указана ли Global Color Table?
if ($flag & 128) {
$pixel = 1 + ($flag & 0b111);
$colors = pow(2, $pixel);
$gctlen = $colors * 3;
// читаем Global Color Table
$gct = fread($fi, $gctlen);
// не вставил ли уже туда злоумышленник „<?“?
// меняем у такой комбинации последний бит — это малозаметно,
// безопасность важнее!
if (strpos($gct, '<?') !== false) {
$gct = str_replace('<', '=', $gct);
}
if (!@fwrite($fo, $lsd . $gct)) return false;
} else {
$gctlen = 0;
if (!@fwrite($fo, $lsd)) return false;
}
// пишем комментарий с куском защиты
fwrite($fo, "!\xfe" . chr(strlen($protect)) . $protect . "\0");
if (@stream_copy_to_stream($fi, $fo) != $controllen - 6 - 7 - $gctlen) return false;
return true;
}В коде есть два места где я ищу не встретилась ли комбинация <? раньше, чем я вставляю защиту — это в заголовочном куске (IHDR) в ПНГ и в глобальной таблице цветов у ГИФа.
В случае ПНГ я анализирую заголовок, чтобы понять как там оказалась эта комбинация, она валидна только в первых восьми байтах (на зло остаётся всего 6 байт), кроме того, в этом случае я контролирую размер заголовка, в стандарте там указано 13 байт, хотя, кажется, размер не ограничивается. В общем, в шести байтах особо не разгуляешься, всё что я придумал туда засунуть — `rm *`, т. е. очистка текущего каталога. На этот случай, я контролирую появление символа `. На мой взгляд, вероятность встретить в нормальном ПНГ в заголовке <? после которого идёт ` ничтожно мала.
С ГИФом проще — меняю младший бит у цвета, это вряд ли будет заметно, а безопасность важнее.
Защита от внедрения PHP-кода в картинки (JPEG, GIF, PNG)
На «Хабре» всплыла тема атаки, старой как мир — заливаем на сайт картинку, у которой внутри (в поле комментариев) есть
Во всех форматах принятых в вебе, есть поле, позволяющее вставить такие данные. В JPEG есть специальные секции APPn и COM, в PNG — iTXt, tEXt и можно создавать произвольные секции, в GIF — блоки расширений, в SVG можно использовать обычный
В качестве абсолютного метода защиты на «Хабре» предлагается пропустить картинку через
Можно ещё попробовать вырезать все секции файла, куда можно запихнуть текстовую информацию, но это не спасёт от файлов, где ПХП вставлен прямо в графическую часть, так, чтобы детектор валидности картинки (в ПХП для этого обычно используют getimagesize) не спотыкался на файле.
Мне кажется, я придумал более удачный метод защиты.
В загруженный файл можно добавить первой секцией (сразу после заголовка) свой собственный кусок
# смотрим что внутри файла
bolk@Bolk ~ $ hexdump -Cn128 sample.jpg
00000000 ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 |......JFIF......|
00000010 00 01 00 00 ff fe 00 1a 3c 3f 70 68 70 20 5f 5f |........<?php __|
00000020 68 61 6c 74 5f 63 6f 6d 70 69 6c 65 72 28 29 3b |halt_compiler();|
00000030 ff db 00 43 00 02 02 02 02 02 01 02 02 02 02 03 |...C............|
00000040 02 02 03 03 06 04 03 03 03 03 07 05 05 04 06 08 |................|
00000050 07 09 08 08 07 08 08 09 0a 0d 0b 09 0a 0c 0a 08 |................|
00000060 08 0b 0f 0b 0c 0d 0e 0e 0f 0e 09 0b 10 11 10 0e |................|
00000070 11 0d 0e 0e 0e ff db 00 43 01 02 03 03 03 03 03 |........C.......|
# запускаем
bolk@Bolk ~ $ php sample.jpg
????JFIF??Т. е. на экран выводится бинарный заголовок JPEG, первая секция, а потом интерпретатор дальше просто не идёт. Будет дальше ход хакера или нет, он не проинтерпретируется.
Приватный режим в Сафари под iOS
Честно сказать, не знаю для чего это может быть нужно, но это одна из вещей, которая если понадобится, то потом не найдёшь. Джаваскрипт, который позволяет определить запущен ли мобильный Сафари (под Айос) в приватном режиме:
function isPrivateMode() {
var s = window.sessionStorage, key = '';
try {
s.setItem(key, '');
s.removeItem(key);
} catch (e) {
if (e.code === DOMException.QUOTA_EXCEEDED_ERR && !s.length) {
return true;
} else {
throw e;
}
}
return false;
}
// приватный режим включен?
alert(isPrivateMode());Оригинальный код основан на следующем факте — в приватном режиме мобильный Сафари запрещает доступ к sessionStorage. Я только обернул скрипт в функцию и переименовал переменные.
Патч модуля bcompiler для PHP 5.4
Мы тут по работе пытаемся закешировать
Я посидел над сообщениями при компиляции, посмотрел что другие ребята делали в модуле xcache для того, чтобы он заработал в 5.4, посмотрел внутренние структуры ПХП и родил патч, который позволяет успешно скомпилировать модуль. Модуль мои простенькие тесты проходит, но это правда не значит, что всё действительно работает.
Добавлено позднее: на более сложных тестах модуль Неудобно только то, что модуль не позволяет работать с объектами и анонимными функциями из ПХП 5.3 (которые тоже объекты), а так же компилирует всё в файл. Но второй недостаток легко обходится:Патч и немного кода.
diff -wrdu bcompiler-1.0.2/bcompiler_zend.c bcompiler-1.0.2.new/bcompiler_zend.c
--- bcompiler-1.0.2/bcompiler_zend.c 2011-06-09 15:58:42.000000000 +0400
+++ bcompiler-1.0.2.new/bcompiler_zend.c 2012-07-24 21:59:30.000000000 +0400
@@ -471,12 +471,6 @@
apc_serialize_zend_class_entry(zv->obj.ce, NULL, 0, NULL, 0 TSRMLS_CC);
#endif
break;
-#ifdef ZEND_ENGINE_2_4
- case IS_CLASS:
- case IS_SCALAR:
- case IS_NUMERIC:
- break;
-#endif
default:
/* The above list enumerates all types. If we get here,
* something very very bad has happened. */
@@ -551,12 +545,6 @@
apc_create_hashtable(&zv->obj.properties, apc_create_zval, NULL, sizeof(zval *) TSRMLS_CC);
#endif
break;
-#ifdef ZEND_ENGINE_2_4
- case IS_CLASS:
- case IS_SCALAR:
- case IS_NUMERIC:
- break;
-#endif
default:
/* The above list enumerates all types. If we get here,
* something very very bad has happened. */
@@ -636,8 +624,10 @@
}
SERIALIZE_SCALAR(arg_info->allow_null , zend_bool);
SERIALIZE_SCALAR(arg_info->pass_by_reference, zend_bool);
+#ifndef ZEND_ENGINE_2_4
SERIALIZE_SCALAR(arg_info->return_reference , zend_bool);
SERIALIZE_SCALAR(arg_info->required_num_args, int);
+#endif
#ifdef ZEND_ENGINE_2_1
if (BCOMPILERG(current_write) > 20) {
# ifdef ZEND_ENGINE_2_4
@@ -667,8 +657,10 @@
else ZS2SR(arg_info->class_name) = NULL;
DESERIALIZE_SCALAR(&arg_info->allow_null, zend_bool);
DESERIALIZE_SCALAR(&arg_info->pass_by_reference, zend_bool);
+#ifndef ZEND_ENGINE_2_4
DESERIALIZE_SCALAR(&arg_info->return_reference, zend_bool);
DESERIALIZE_SCALAR(&arg_info->required_num_args, int);
+#endif
#ifdef ZEND_ENGINE_2_1
if (BCOMPILERG(current_version) > 20) {
# ifdef ZEND_ENGINE_2_4
@@ -883,7 +875,9 @@
#else
SERIALIZE_SCALAR(*(zce->refcount) , int);
#endif
+#ifndef ZEND_ENGINE_2_4
SERIALIZE_SCALAR(zce->constants_updated, zend_bool);
+#endif
#ifdef ZEND_ENGINE_2
SERIALIZE_SCALAR(zce->ce_flags, zend_uint);
#endif
@@ -930,15 +924,15 @@
* and all the functions. */
BCOMPILER_DEBUG(("-----------------------------\nBUILTIN:\n"));
count = 0;
- if (zce->type == ZEND_INTERNAL_CLASS && zce->builtin_functions) {
- for (zfe = (zend_function_entry*)zce->builtin_functions; zfe->fname != NULL; zfe++) {
+ if (zce->type == ZEND_INTERNAL_CLASS && zce->info.internal.builtin_functions) {
+ for (zfe = (zend_function_entry*)zce->info.internal.builtin_functions; zfe->fname != NULL; zfe++) {
count++;
}
}
SERIALIZE_SCALAR(count, int);
for (i = 0; i < count; i++) {
- apc_serialize_zend_function_entry((zend_function_entry *)&zce->builtin_functions[i] TSRMLS_CC);
+ apc_serialize_zend_function_entry((zend_function_entry *)&zce->info.internal.builtin_functions[i] TSRMLS_CC);
}
#ifndef ZEND_ENGINE_2
/** old code: SERIALIZE_SCALAR(zce->handle_function_call, void*); */
@@ -974,12 +968,12 @@
/* do reflection info (file/start/end) !! TODO: check for PHP6 !! */
#ifdef ZEND_ENGINE_2
if (BCOMPILERG(current_write) >= 25) {
- char* fname = bcompiler_handle_filename(zce->filename TSRMLS_CC);
+ char* fname = bcompiler_handle_filename(zce->info.user.filename TSRMLS_CC);
apc_serialize_string(fname TSRMLS_CC);
- SERIALIZE_SCALAR(zce->line_start, int);
- SERIALIZE_SCALAR(zce->line_end, int);
- apc_serialize_zstring(zce->doc_comment, zce->doc_comment_len TSRMLS_CC);
- SERIALIZE_SCALAR(zce->doc_comment_len, int);
+ SERIALIZE_SCALAR(zce->info.user.line_start, int);
+ SERIALIZE_SCALAR(zce->info.user.line_end, int);
+ apc_serialize_zstring(zce->info.user.doc_comment, zce->info.user.doc_comment_len TSRMLS_CC);
+ SERIALIZE_SCALAR(zce->info.user.doc_comment_len, int);
}
#endif
@@ -1089,7 +1083,9 @@
*zce->refcount = 1; /* val: to avoid memort leaks */
}
#endif
+#ifndef ZEND_ENGINE_2_4
DESERIALIZE_SCALAR(&zce->constants_updated, zend_bool);
+#endif
#ifdef ZEND_ENGINE_2
DESERIALIZE_SCALAR(&zce->ce_flags, zend_uint);
#endif
@@ -1184,11 +1180,11 @@
zend_class_entry.builtin_functions member */
DESERIALIZE_SCALAR(&count, int);
- zce->builtin_functions = NULL;
+ zce->info.internal.builtin_functions = NULL;
if (count > 0) {
- zce->builtin_functions = (zend_function_entry*) ecalloc(count+1, sizeof(zend_function_entry));
+ zce->info.internal.builtin_functions = (zend_function_entry*) ecalloc(count+1, sizeof(zend_function_entry));
for (i = 0; i < count; i++) {
- apc_deserialize_zend_function_entry((zend_function_entry*)&zce->builtin_functions[i] TSRMLS_CC);
+ apc_deserialize_zend_function_entry((zend_function_entry*)&zce->info.internal.builtin_functions[i] TSRMLS_CC);
}
}
#ifndef ZEND_ENGINE_2
@@ -1237,11 +1233,11 @@
if (BCOMPILERG(current_version) >= 25) {
char* fname;
apc_create_string_u(&fname, -1 TSRMLS_CC);
- zce->filename = fname ? fname : estrdup(BCOMPILERG(current_filename));
- DESERIALIZE_SCALAR(&zce->line_start, int);
- DESERIALIZE_SCALAR(&zce->line_end, int);
- apc_create_string_u(ZS2SP(zce->doc_comment), -1 TSRMLS_CC);
- DESERIALIZE_SCALAR(&zce->doc_comment_len, int);
+ zce->info.user.filename = fname ? fname : estrdup(BCOMPILERG(current_filename));
+ DESERIALIZE_SCALAR(&zce->info.user.line_start, int);
+ DESERIALIZE_SCALAR(&zce->info.user.line_end, int);
+ apc_create_string_u(ZS2SP(zce->info.user.doc_comment), -1 TSRMLS_CC);
+ DESERIALIZE_SCALAR(&zce->info.user.doc_comment_len, int);
}
#endif
@@ -1564,7 +1560,7 @@
#ifdef ZEND_ENGINE_2_4
SERIALIZE_SCALAR(zoa->last_literal, zend_uint);
- SERIALIZE_SCALAR(zoa->size_literal, zend_uint);
+ //SERIALIZE_SCALAR(zoa->size_literal, zend_uint);
for (i = 0; i < (int) zoa->last_literal; i++) {
bc_serialize_zend_literal(&zoa->literals[i] TSRMLS_CC);
}
@@ -1572,7 +1568,7 @@
SERIALIZE_SCALAR(zoa->refcount[0], zend_uint);
SERIALIZE_SCALAR(zoa->last, zend_uint);
- SERIALIZE_SCALAR(zoa->size, zend_uint);
+ //SERIALIZE_SCALAR(zoa->size, zend_uint);
/* If a file 'A' is included twice in a single request, the following
* situation can occur: A is deserialized and its functions added to
@@ -1595,7 +1591,7 @@
SERIALIZE_SCALAR(zoa->T, zend_uint);
SERIALIZE_SCALAR(zoa->last_brk_cont, zend_uint);
- SERIALIZE_SCALAR(zoa->current_brk_cont, zend_uint);
+ //SERIALIZE_SCALAR(zoa->current_brk_cont, zend_uint);
#ifndef ZEND_ENGINE_2
SERIALIZE_SCALAR(zoa->uses_globals, zend_bool);
#endif
@@ -1608,8 +1604,8 @@
}
apc_serialize_hashtable(zoa->static_variables, apc_serialize_zval_ptr TSRMLS_CC);
assert(zoa->start_op == NULL);
- SERIALIZE_SCALAR(zoa->return_reference, zend_bool);
- SERIALIZE_SCALAR(zoa->done_pass_two, zend_bool);
+ //SERIALIZE_SCALAR(zoa->return_reference, zend_bool);
+ //SERIALIZE_SCALAR(zoa->done_pass_two, zend_bool);
fname = bcompiler_handle_filename(zoa->filename TSRMLS_CC);
apc_serialize_string(fname TSRMLS_CC);
if (fname) efree(fname);
@@ -1628,9 +1624,9 @@
#endif
SERIALIZE_SCALAR(zoa->fn_flags, zend_uint);
SERIALIZE_SCALAR(zoa->required_num_args, zend_uint);
- SERIALIZE_SCALAR(zoa->pass_rest_by_reference, zend_bool);
+ //SERIALIZE_SCALAR(zoa->pass_rest_by_reference, zend_bool);
- SERIALIZE_SCALAR(zoa->backpatch_count, int);
+ //SERIALIZE_SCALAR(zoa->backpatch_count, int);
#ifdef ZEND_ENGINE_2_3
SERIALIZE_SCALAR(zoa->this_var, zend_uint);
#else
@@ -1642,7 +1638,7 @@
#ifdef ZEND_ENGINE_2_1
if (BCOMPILERG(current_write) >= 0x0007) {
SERIALIZE_SCALAR(zoa->last_var, int);
- SERIALIZE_SCALAR(zoa->size_var, int);
+ //SERIALIZE_SCALAR(zoa->size_var, int);
for (i = 0; i < zoa->last_var; i++) {
SERIALIZE_SCALAR(zoa->vars[i].name_len, int);
apc_serialize_zstring(ZS2S(zoa->vars[i].name), ZLEN(zoa->vars[i].name_len) TSRMLS_CC);
@@ -1706,7 +1702,7 @@
#ifdef ZEND_ENGINE_2_4
DESERIALIZE_SCALAR(&zoa->last_literal, zend_uint);
- DESERIALIZE_SCALAR(&zoa->size_literal, zend_uint);
+ //DESERIALIZE_SCALAR(&zoa->size_literal, zend_uint);
zoa->literals = NULL;
if (zoa->last_literal) {
zoa->literals = (zend_literal*) ecalloc(zoa->last_literal, sizeof(zend_literal));
@@ -1722,7 +1718,7 @@
*zoa->refcount = 1; /* val: to avoid memort leaks */
}
DESERIALIZE_SCALAR(&zoa->last, zend_uint);
- DESERIALIZE_SCALAR(&zoa->size, zend_uint);
+ //DESERIALIZE_SCALAR(&zoa->size, zend_uint);
zoa->opcodes = NULL;
@@ -1736,7 +1732,7 @@
DESERIALIZE_SCALAR(&zoa->T, zend_uint);
DESERIALIZE_SCALAR(&zoa->last_brk_cont, zend_uint);
- DESERIALIZE_SCALAR(&zoa->current_brk_cont, zend_uint);
+ //DESERIALIZE_SCALAR(&zoa->current_brk_cont, zend_uint);
#ifndef ZEND_ENGINE_2
DESERIALIZE_SCALAR(&zoa->uses_globals, zend_bool);
#endif
@@ -1749,10 +1745,10 @@
}
apc_create_hashtable(&zoa->static_variables, apc_create_zval, NULL, sizeof(zval *) TSRMLS_CC);
- zoa->start_op = NULL;
+ //zoa->start_op = NULL;
- DESERIALIZE_SCALAR(&zoa->return_reference, zend_bool);
- DESERIALIZE_SCALAR(&zoa->done_pass_two, zend_bool);
+ //DESERIALIZE_SCALAR(&zoa->return_reference, zend_bool);
+ //DESERIALIZE_SCALAR(&zoa->done_pass_two, zend_bool);
apc_create_string_u(&fname, -1 TSRMLS_CC);
if (fname) {
zoa->filename = zend_set_compiled_filename(fname TSRMLS_CC);
@@ -1798,8 +1794,8 @@
zoa->fn_flags &= ~ZEND_ACC_IMPLEMENTED_ABSTRACT; /* this is set later by zend_do_inheritance() */
#endif
DESERIALIZE_SCALAR(&zoa->required_num_args, zend_uint);
- DESERIALIZE_SCALAR(&zoa->pass_rest_by_reference, zend_bool);
- DESERIALIZE_SCALAR(&zoa->backpatch_count, int);
+ //DESERIALIZE_SCALAR(&zoa->pass_rest_by_reference, zend_bool);
+ //DESERIALIZE_SCALAR(&zoa->backpatch_count, int);
#ifdef ZEND_ENGINE_2_3
DESERIALIZE_SCALAR(&zoa->this_var, zend_uint);
#else
@@ -1811,15 +1807,15 @@
#ifdef ZEND_ENGINE_2_1
if (BCOMPILERG(current_version) >= 0x0007) {
DESERIALIZE_SCALAR(&zoa->last_var, int);
- DESERIALIZE_SCALAR(&zoa->size_var, int);
- if (zoa->size_var > 0) {
+ //DESERIALIZE_SCALAR(&zoa->size_var, int);
+ /*if (zoa->size_var > 0) {
zoa->vars = ecalloc(zoa->size_var, sizeof(zoa->vars[0]));
for (i = 0; i < zoa->last_var; i++) {
DESERIALIZE_SCALAR(&(zoa->vars[i].name_len), int);
apc_create_string_u(ZS2SP(zoa->vars[i].name), -1 TSRMLS_CC);
DESERIALIZE_SCALAR(&(zoa->vars[i].hash_value), ulong);
}
- }
+ }*/
}
#endif
diff -wrdu bcompiler-1.0.2/php_bcompiler.c bcompiler-1.0.2.new/php_bcompiler.c
--- bcompiler-1.0.2/php_bcompiler.c 2011-06-09 15:58:42.000000000 +0400
+++ bcompiler-1.0.2.new/php_bcompiler.c 2012-07-24 21:27:31.000000000 +0400
@@ -571,7 +571,7 @@
#ifndef ZEND_ENGINE_2
if (i > n_old_class)
#else
- if (zc->filename && strcmp(zc->filename, real_path) == 0)
+ if (zc->info.user.filename && strcmp(zc->info.user.filename, real_path) == 0)
#endif
{
SERIALIZE_SCALAR(BCOMPILER_CLASS_ENTRY, char)
@@ -632,7 +632,7 @@
#ifndef ZEND_ENGINE_2
if (i > n_old_class)
#else
- if (zc->filename && strcmp(zc->filename, real_path) == 0)
+ if (zc->info.user.filename && strcmp(zc->info.user.filename, real_path) == 0)
#endif
{
#if PHP_MAJOR_VERSION >= 6
diff -wrdu bcompiler-1.0.2/php_bcompiler.h bcompiler-1.0.2.new/php_bcompiler.h
--- bcompiler-1.0.2/php_bcompiler.h 2011-06-09 15:58:42.000000000 +0400
+++ bcompiler-1.0.2.new/php_bcompiler.h 2012-07-24 22:13:31.000000000 +0400
@@ -22,7 +22,7 @@
#ifndef PHP_BCOMPILER_H
#define PHP_BCOMPILER_H
-#define PHP_BCOMPILER_VERSION "1.0.2"
+#define PHP_BCOMPILER_VERSION "1.0.2 (bolk) "
#ifdef HAVE_CONFIG_H
#include "config.h"$fp = fopen('php://memory', 'rw');
bcompiler_write_header($fp);
bcompiler_write_function($fp, 'тут имя вашей функции, которую надо скомпилировать');
bcompiler_write_footer($fp);
fseek($fp, 0);
$compiled = stream_get_contents($fp); // байт-код теперь в переменной $compiled
fclose($fp);
Замыкания и Джаваскрипт
Все знают, что зона видимости в JS создаётся только внутри функций (если не брать в расчёт конструкцию «let» из JS 1.7):
var v = 5;
!(function() {
var v = 6;
})();
alert(v); // будет 5Я тут подумал, что ведь есть и второй способ (я
var x = 1, y = 0;
// конструкция «let», JS 1.7+
let (x = x+10, y = 5) {
alert(x+y); // 16
}
alert(x+y); // 1
// конструкция «with»
with ({ x: x+10, y: 5 }) {
alert(x+y); // 16
}
alert(x+y); // 1Не так уж и много отличий, только выражением «with» быть не умеет, но можно было бы и научить.
Двухбуквенные домены и ИЕ
Про проблему с двухбуквенными доменами в браузере Микрософта знают наверное, очень многие. Если вкратце, считается, что на двухбуквенных доменах второго уровня «Эксплорер» не ставит куки.
Отчасти так оно и есть.
Сделано это вот для чего. Некоторые страны (например, Великобритания) запрещают регистацию домена второго уровня для своих нужд, домен должен находиться на третьем уровне в зоне специальных доменов второго уровня. Если бы я родился и жил в Лондоне, мой блог мог бы располагаться по адресу bolknote.co.uk.
Вот отсюда и родилось это ограничение — так как на домене третьего уровня в некоторых странах располагается куча чужих друг другу сайтов, они могли бы читать и писать куки друг друга через общий домен (и воровать данные авторизации, например), но браузеры не озаботились механизмом предотвращения этой угрозы.
В «Экплорере» (скорее всего это произошло в шестой версии) защиту устроили просто — если в домене второго уровня две буквы и он не располагается на домене общего назначения (.com, .net и т. п.), то это специальный домен из вышеописанного случая и ставить куку на него нельзя.
Но тогда как же, чёрт возьми, поступает «Яндекс» со своих двухбуквенным «ya.ru»? Есть
Разочарую — её нет, есть исключения.
Ребята из «Микрософта» через

Т. е. если домен встречается в теге «tld» или содержит только две буквы на втором уровне, то куку ставить нельзя, но если он встретился в теге «domain» — то можно. Посмотрите на скриншот, домен «Яндекса» содержится в списке исключений. Естественно, список может меняться при обновлении браузера.
Остальным браузерам так же приходится решать эту проблему. Например, ФФ содержит примерно такой же список:
# примерное количество значений в этом списке у ФФ:
bolk@Bolk ~ $ grep -aF chel.ru /Applications/Firefox.app/Contents/MacOS/XUL | strings | fgrep . | fgrep -v / | sort -u | wc -l
4019
# двадцать последних (по алфавиту) значений:
bolk@Bolk ~ $ grep -aF chel.ru /Applications/Firefox.app/Contents/MacOS/XUL | strings | fgrep . | fgrep -v / | sort -u | tail -20
z.bg
z.se
za.com
za.net
za.org
zachpomor.pl
zagan.pl
zakopane.pl
zaporizhzhe.ua
zarow.pl
zgora.pl
zgorzelec.pl
zgrad.ru
zhitomir.ua
zj.cn
zlg.br
zoological.museum
zoology.museum
zp.ua
zt.uaНе знаю все ли четыре тысячи относятся к обсуждаемому списку (кстати, в нём всего 133 российских домена), но такой список точно существует.
Тот же список есть и в «Хроме» (на моём «Маке» он содержится в файле «Google Chrome Framework» и состоит из более 3,5 тысяч значений), в «Сафари» ничего похожего я обнаружить не смог, то ли такого механизма там нет, то ли спрятано глубже, чем мне позволяет искать моя лень. «Опера» держит свой список в файле «public_domains.dat», в нём на настоящий момент 4291 значение.
Таким образом, если вы владелец двухбуквенного домена и вам не повезло, у вас не работают куки как положено, пишите на адреса техподдержки производителей браузеров, а пока делайте редирект на домен третьего уровня (например, на «www»).
ПХП: красавица и чудовище
Одна из превосходнейших вещей в ПХП — обработчики протоколов. Они позволяют работать с разнообразными источниками данных как с файлами. Встроенные протоколы умеют понимать ftp, http, data URL, ssh2, rar, zlib и ещё несколько менее известных штук. Т. е. традиционный путь до файла в ПХП на самом деле является УРЛом, по виду которого интерпретатор решает какой обработчик за него ответственен. Если протокол (в терминах УРЛ) не указан, считается, что идёт обращение к локальному файлу. Язык так же позволяет создавать свои обработчики.
ПХП единственный из известных мне языков, где я могу с лёгкостью сделать такие вещи:
copy('ftp://user:pass@example.org/pub/somefile.txt', '/tmp/somefile.txt');
$var = file_get_contents('https://example.org/index.html');
# и при этом
copy('/tmp/somefile', '/tmp/anotherfile.txt');
$var = file_get_contents('/etc/passwd');Немного более сложный синтаксис позволяет сделать и более сложные вещи:
$post = http_build_query([
'login' => $login,
'pass' => $password,
]);
$options = [
'http' => [
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded\r\n'.
'Content-length: ' . strlen($post),
'content' => $post,
'proxy'=>"tcp://192.168.1.1:3128"
]
];
$result = file_get_contents('http://example.org/action.php', false, stream_context_create($options));От полного изящества далеко,
Что сделано из рук вон плохо — это получение заголовков ответа для протоколов HTTP и HTTPS: ответ просто помещается в локальную переменную http_response_header, это печальное и достойное сожаления решение.
Более неявное поведение трудно и выдумать. Оно настолько неявное, что многие о нём даже не подозревают. Ещё отвратительно, что эта переменная содержит заголовки не в виде ключ/значение, а просто набор строк:
file_get_contents('//bolknote.ru');
var_dump($http_response_header);вернёт:
array(14) {
[0]=>
string(15) "HTTP/1.1 200 OK"
[1]=>
string(13) "Server: nginx"
[2]=>
string(35) "Date: Wed, 11 Jul 2012 19:50:10 GMT"
… и так далееКак такой непродуманный функционал может являться продолжением такой прекрасной штуки мне непонятно.
Ночная сборка «Оперы» 12.50 посягнула на браузерные префиксы
Вышла первая ночная сборка «Оперы» 12.50. Из горько разочаровавших меня вещей — теперь все свойства поддерживаются не только с «оперовским» префиксом
As previously announced, a subset of widely used -webkit- prefixed CSS properties are now mapped to their -o- counterpart. The supported properties include -
webkit-box-shadow, -webkit-transform, -webkit-transform-origin, -webkit-border-radius, -webkit-border-top-left-radius, -webkit-border-top-right-radius, -webkit-border-bottom-left-radius, -webkit-border-bottom-right-radius, -webkit-transition, -webkit-transition-delay, -webkit-transition-duration, -webkit-transition-property, and -webkit-transition-timing-function. You should, however, not rely solely on -webkit- prefixes!
С этого дня «Хром» — новый «Эксплорер» времён «войны браузеров» и «Опера» ему в этом потакает! Расшифровываю: «Опера» первая свыклась с мыслью, что всё больше сайтов делают только под «Хром» (как
Кроме того, это полностью дискредитирует идею префиксов. Ведь идея была в том, что браузеры реализовывали свойства на различных этапах правки документации и их синтаксис мог различаться от браузера к браузеру. Так уже несколько раз случалось. Кроме того, у всех были собственные глюки и префиксы были хорошим средством этим управлять.
Насколько я помню, такое уже случалось, кажется, «Опера» поддержала
Эх, «Опера» как же ты так?
Бонжур, примите файл!
На «Маке» есть «Бонжур» — протокол, позволяющий регистрировать сервисы и смотреть какие сервисы зарегистрированы в локальной сети. Удобная штука, через неё «Айтюнс» делится музыкой с соседями по локалке, через неё работает «Эйрдроп» — служба для быстрого обмена файлами на «Маке».
В общем, пришла мне в голову идея — нельзя ли
Если её запустить без параметров, то она ждёт анонса специальной службы и, если находит, коннектится на указанный IP по порту 1111 и выдаёт переданный файл на экран. Если запустить с параметром, ожидается, что это имя файла для передачи, далее анонсируется служба, ожидается соединение на порт 1111 и туда выдаётся файл.
При передаче контролируется контрольная сумма (md5), а файл сжимается утилитой gzip. Для анонса и получения анонсированных служб используется утилита
bash bonfile.sh > somefile # запуск клиента (ожидаем приём файла)
# или
bash bonfile.sh somefile # запуск сервера (ожидаем подключения клиента)Как определить модель iPad через JavaScript?
На «Хабре» наткнулся на любопытный код на JS, который определяет с каким Айпадом мы имеем дело — первым, вторым или третьим. Третий ото всех остальных отличить просто — у него в свойстве «devicePixelRatio» значение „2“ записано. А первый от второго как?
Оказывается способ есть — надо в событии «devicemotion» проверить есть ли у устройства акселерометр. Не знаю для чего это всё может пригодиться (разве что отключать
Я немного переписал код, сделал из него функцию, чуть упростил и навёл порядок. Получился вот такой код:
// функция для определения модели Айпада
function detectIPadVersion(callback, undefined) {
if ('ondevicemotion' in window) {
window.addEventListener('devicemotion', function self(event) {
if (~navigator.platform.indexOf('iPad')) {
var version = event.acceleration ? (window.devicePixelRatio === 2) + 2 : 1;
}
window.removeEventListener('devicemotion', self);
callback(version);
}, false);
} else {
callback(undefined);
}
};
// пример вызова, в callback будет передан номер модели (1,2 или 3) или undefined, в случае, если это не iPad
detectIPadVersion(alert);Opera 12 CSS hack
Вышла «Опера» 12, многое поменялось, на некоторых моих сайтах едут стили. В частности, на «Маке» «Опера» поменяла поведение стиля
Пришлось придумывать
Хак получился вот такой:
@media (min-resolution: .001dpcm) {
_:-o-prefocus, body { /* стиль только для Opera 12.00+ */
background: red;
};
}Соответственно, на место «body» надо поставить требуемый тег или селектор.
«Граул» и протокол ГНТП
У меня тут «Граул» обновился на «Маке» и я краем глаза увидел, что появилась поддержка
«Граул», для тех кто не в курсе, программулина, которая позволяет различным сторонним программам формировать всплывающие уведомления в едином унифицированном виде. Кстати, существует и версия для «Виндоуз».
Включается поддержка ГНТП специальной галочкой в настройках, после чего можно отсылать свои уведомления. Там же можно задать и пароль, чтобы ни одна сволочь снаружи не смогла

Протокол несложный и поддерживает три команды: можно зарегистрировать своё приложения и его типы сообщений, сформировать сообщение и подписаться на события. Для параноиков поддерживается ещё и шифрование (AES, DES или 3DES на выбор).
Прежде чем ваше приложение отошлёт сообщение, оно должно «зарегистрироваться» в «Грауле» — представиться и рассказать что за сообщения оно будет слать и как их оформлять. Ниже на скриншоте показан список зарегистрированных приложений, включая мой скрипт.

Я тут попробовал поработать с ГНТП из bash-скрипта, получилось удобоваримо, заодно вспомнил как работать с утилитой expect (можно было бы использовать netcat, но telnet плюс expect — интереснее и более гибко).
Увы, у expect под «Маком»
Внутри файла, в конце есть пример, который показывает как отослать сообщение и, в случае неудачи, попробовать сначала зарегистрировать своё приложение и потом отправить своё сообщение ещё раз:
# Пробуем отослать сообщение
cmd='GrowlSendNotify "" "" "Hello all!"'
eval $cmd || (
GrowlSendRegister && eval $cmd
)Внутри моего скрипта есть ещё функция получения IP локальной машины, на самом деле она там лишняя — «Граул» слушает на всех адресах, но мне было интересно написать такую функцию.
Стоимость вызова функции в ПХП
В комментариях к предыдущей заметке мне напомнили о немаловажной подробности, которую я упустил из вида в прошлый раз — в ПХП вызов функции дорогое удовольствие. Как пишут на демотиваторах «по возможности избегайте этого».
Например, если в прошлом примере с массивами array_push заменить на добавление элемента в массив через „[]“, то производительность сразу возрастёт на 0,04 секунды.
В самом деле, есть масса ситуаций, когда вызов функции можно заменить конструкцией или получением данных из константы, так же существуют частные случаи, когда такая замена возможна:
# занесение элемента в массив:
array_push($stack, $element);
$stack[] = $element;
# получение версии ПХП, есть ещё
# PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION, PHP_VERSION_ID и PHP_EXTRA_VERSION
if (phpversion() === "5.0.0") {}
if (PHP_VERSION === "5.0.0") {}
# способ подключения ПХП
if (php_sapi_name() === 'cli') {}
if (PHP_SAPI === 'cli') {}
# проверка существования ключа, но, увы, это не одно и тоже:
# isset не покажет true, если ключ существует, но значение null
if (array_key_exists('key1', $arr) && array_key_exists('key2', $arr)) {}
if (isset($arr['key'], $arr['key2'])) {}
# для массивов, ключи которых не перекрываются (операция «+» добавит только те ключи,
# которые отсутствуют в первом массиве)
$arr = array_merge($arr1, $arr2);
$arr = $arr1 + $arr2;
# операционная система
if (php_uname('s') === 'Darwin') {}
if (PHP_OS === 'Darwin') {}
# получение переменной окружения. Есть отличие: массив $_ENV может быть перезаписан другим кодом,
# при этом реальное значение переменной окружения не изменится, кроме того,
# getevn не сгенерирует предупреждение, если значения с таким ключом нет
if (getenv('VARNAME') === 'value') {}
if ($_ENV['VARNAME'] === 'value') {}Наверняка есть и ещё случаи, которые я не смог сходу вспомнить, буду благодарен, если
Производительность различных реализаций стека в ПХП

Я совсем перестал писать на технические темы в блоге. Это объяснимо: я много разрабатываю, но поделиться нечем, всё это весьма специфично для системы документооборота. Недавно, например, я думал над тем какой бы язык применить для описания фильтров в системе. Они должны срабатывать много и часто, и опосредованно управлять движением документов. Пользователь писать самостоятельно их не будет (или почти не будет), они будут составяться «конструктором», но система
Выбрал обратную польскую запись. Она компактна, просто парсится, для неё не нужно строить дерево, там нет скобок, но при этом по мощности польностью эквивалентна привычной всем со школы записи. Идея в том, что сначала на стек записываются операнды, а потом указывается операция, которая берёт их со стека и записывает обратно результат:
# традиционная запись:
(5 + 7) * 3
# обратная польская:
5 7 + 3 *
# а можно так:
3 5 7 + *Так как упор на производительность, я реализовал простенькую стековую машинку с операциями сложения, вычитания, умножения, проверки на равенство и дублирования вершины стека с использованием простых массивов, ArrayObject и SplStack, чтобы проверить как в ПХП (на котором пишется наша система) дела с производительностью стека. ArrayObject тут скорее для полноты картины, так как у него нет метода pop, который доставал бы элемент с вершины, так что пришлось заменить его на отдельную функцию.
Результаты следующие:
mac@mac ~/Sites/test $ php autoc/stack.php
Array: 0.27216792106628
Array Object: 0.7418909072876
SplStack: 0.29819416999817Как видно, примитивные типы
Для интереса, релизовал примерно то же самое на Пайтоне (только DUP немного иначе работает, для скорости):
from __future__ import print_function
import time
input = '10 20 100 + - 20 * DUP 10 20 + * =';
operations = {
'+': lambda: stack.append(stack.pop() + stack.pop()),
'-': lambda: stack.append(stack.pop() - stack.pop()),
'*': lambda: stack.append(stack.pop() * stack.pop()),
'=': lambda: stack.append(stack.pop() == stack.pop()),
'DUP': lambda: stack.append(stack[0]),
'DUMP': lambda: print(stack)
}
t = time.time()
for x in xrange(0, 10000):
stack = []
for chunk in input.split(' '):
operations.get(chunk, lambda: stack.append(int(chunk)))()
print('Python list: %f' % (time.time() - t))Время выполнения — 0,56 секунды, то есть на ПХП быстрее. Зато, если запустить этот код через PyPy (это интерпретатор с JIT), то результат куда более впечатляет — 0,10 секунды.
Размер имеет значение
Такая ещё вещь в ПХП вызывает мою искреннюю печаль. В руководстве написано, что при приведении переменной к булевскому типу, строка «"0"» трактуется как false:
var_dump( (bool) "0"); // false
var_dump( (bool) "00"); // trueСделано это по определённым причинам. ПХП, исторически, был языком не просто для чайников, а для фанерных чайников, когда снаружи приходят параметры в GET или POST запросе, ПХП их раскладывает в специальные переменные, но все данные имеют тип «строка» (иногда — массив строк).
Дальше, и тут есть своя логика, если снаружи приходит ноль, ПХП решает
Поэтому я называю ПХП языком не с «динамическим приведением типов», а с «нестабильным приведением типов». Потому что его правила приведения содержат исключения.
Такой «строковый ноль» может привести к сюрпризам:
var_dump(array_filter( [ 0, null, false, "00", "0" ])); // останется один элемент — "00"
var_dump(in_array("0", [ false ])); // trueИли вот, например, поведение конструкции «empty» с той же строкой (именно из документации по этой конструкции я узнал о существовании такого нуля, ещё в ПХП3):
$z = "0"; $zz = "00";
var_dump(empty($z)); // true
var_dump(empty($zz)); // falseПри этом, один пробел и всё, ноль перестаёт быть «магическим»:
var_dump(in_array(" 0", [ false ])); // false☁ == ☀
Как знают программисты на ПХП, у этого языка есть одна неприятная особенность: при сравнении с приведением типов („==“) ПХП всё, что по его мнению похоже на число, преобразует в число:
If you compare a number with a string or the comparison involves numerical strings, then each string is converted to a number and the comparison performed numerically.
Причём, «похоже на число» — это не обязательно нечто десятичное:
var_dump("0x01" == "1"); // true
// но при этом
var_dump("0b01" == "1"); // false
var_dump("014" == "12") // falseЭта проблема косвенно проявляется при поиске в массиве. Казалось бы, строкового значения «1.5» в массиве нет:
var_dump(in_array("1.5", ["01.5000"])); // true
// правда есть специальный флаг для строгого стравнения, о котором почему-то мало кто знает:
var_dump(in_array("1.5", ["01.5000"], true)); // falseНо настоящая проблема вот в чём. ПХП, конечно, смотрит, похожа ли строка на то, что он считает числом, но никак не проверяет сможет ли он «съесть то, что укусил»:
var_dump("10000000000000000000000000000000" == "10000000000000000099999999999999"); // trueСюрприз?
Почему так происходит. Всё очень просто. ПХП решает: внутри строки одни цифры, нужно преобразовать в число, в целый это не помещается, поэтому преобразую в число с плавающей точкой. Но число с плавающей точкой хранится с некой точностью, такова плата за его быструю обработку машиной.
В итоге при конвертации происходит потеря точности, левая и правая часть конвертируются в 1e31 (единица с с 31 нулём), которые равны между собой. Это может произойти, например, тогда, когда мы сравниваем «двумя равно» пару хешей, которые содержат только цифры.
Вывод: используете всегда только строгое равенство („===“) и вызовы функций сравнения с флагами, отключающими приведение типов, если только вы чётко не понимаете когда это правило можно нарушить. Но.
Есть конструкция, которой мы управлять не можем:
switch ("1.5") {
case "0001.500": echo "пичалька"; // ещё какая!
}Конструкция «switch» всегда использует нестрогое сравнение, об этом нужно помнить.
Как в JS узнать что объект — массив?
Узнал на семинаре Кантора такую штуку. Как узнать, что объект, который пришёл на вход — массив? Конечно, лучше использовать утиную типизацию, но если нам нужен именно массив, со всей полнотой методов, не проверять же каждый?
Кстати, если
Я до недавнего времени думал, что правильный способ такой:
console.log([] instanceof Array); // trueА редко его используют, потому что конструкция «instanceof» не везде реализована. Я заблуждался. Вот правильный способ:
console.log(Object.prototype.toString.call([]) == '[object Array]'); // trueИ вот почему. Оказывается, у каждого фрейма своя иерархия типов, если один фрейм передаёт во второй
<script>
function Up(arr, iframeWindow) {
alert(arr instanceof Array); // false
alert(arr instanceof iframeWindow.Array); // true
alert(Object.prototype.toString.call([]) == '[object Array]'); // true
}
</script>
<iframe src="data:text/html, <script>parent.Up([], window)</script>"></iframe>Логичность шаблона «объявили и вызвали» в JS
Вчера в комменариях возникло обсуждение семантики синтаксиса «!function () {}()». Мне так кажется, что семантика здесь побоку, использовать функцию для создания зоны видимости — уже костыль (в JS 1.7 есть конструкция «let» для этого), но мне тут подумалось, что ведь можно сделать эту конструкцию семантичной, если использовать забытую конструкцию «with»:
with (function () {
// тело функции
}) call();На мой взгляд, весьма и весьма семантично. Что скажете?
Декларация-вызов в JS
Сегодня на первой части семинара Ильи Кантора по JS присутствовал (к сожалению, на второй части не получится, буду в Москве это время). Немного поговорили о частом паттерне «объявили функцию и тут же вызвали». Используется этот приём (если кто не знаком с ДжаваСкриптом) для изоляции — в этом языке функции единственное, что может создавать зоны видимости.
Я вскользь затронул свой любимый способ — описание функции и вызов через восклицательный знак или тильду, вместо оборачивания скобками. Вообще, в JS непосредственный вызов функции сразу по месту определения возможен, только если декларация находится в контексте выражения. Для этого и нужны традиционные скобки:
(function () {
// скобки вокруг функции говорят интерпретатору, что это выражение
})();Вообще говоря, можно использовать любые средства, чтобы декларация стала частью выражения. Например, можно
Использование тильды или восклицательного знака гораздо лучше скобок по трём причинам:
!function () {
//
}();И,
var a = 5
(function() {})()Причём ошибку, которую найти иногда довольно трудно, да и сообщение о ней не самое дружественное: «TypeError: 5 is not a function». На самом деле, тут пропущена точка с запятой. Интерпретатор считает, что ему встретились две части выражения, между которыми пропущен
Обычно для этого добавляют точку с запятой сразу перед такими блоками.
Но если использовать унарный оператор, то этой проблемы просто нет (поэтому, на самом деле, мы экономим два байта, ещё и точка с запятой не обязательна):
var a = 5
!function () {}()Тут интерпретатор, встретив унарный оператор, понимает, что предыдущее выражение закончилось и началась новая конструкция.
Ещё недостаток «Гоу»
В отличие от многих языков, в «Гоу» символ решётки не позволяет комментировать строку. В упомянутых языках это сделано не зря. В различных
Сам же язык, встретив первой строкой
В «Гоу» такого нет. Несмотря, что в первой версии появилась команда «go run», позволяющая запускать программы из командной строки, не компилируя их (на самом деле компилируя, но результат не сохраняется), я не могу указать в первой строке «#!/usr/local/bin/go run», «Гоу» меня отругает:
bolk@dhcp-174 ~ $ ./main.go
package :
main.go:1:1: illegal character U+0023 '#'Прискорбно.