Фреймверк CodeIgniter - замечательная вещь для небольших проектов. И хотя недавно уже вышел предварительный релиз 2.0, многие предпочитают пользоваться старой и проверенной веткой 1.7.x, ибо эта ветка уже излазана вдоль и поперек, и в интернете накоплен обширный опыт ее использования.
Однако, в CodeIgniter версии 1.7.1 и 1.7.2 наличествует баг, который не позволяет сделать правильную загрузку файлов на сервер. Причем, данный баг у некоторых разработчиков (в определенных случаях) получается "лечить", а у других - нет. Первые, у кого "получилось" вылечить, обливают говном вторых, рассказывая про криворукость оппонентов. Вторые остаются в непонятках, и продолжают утверждать, что у них как не работала закачка, так и не работает.
Давайте теперь разбираться, что за баг такой странный, и как с ним бороться.
Внешнее проявление бага: При закачке файла, организованной с помощью класса Upload, метод $this->upload->do_upload() возвращает FALSE, что означает ошибку при загрузке файла. При этом метод $this->upload->display_errors() возвращает строку вида:
Mime type "application/x-msdownload"
The filetype you are attempting to upload is not allowed.
Данная ошибка возникает если при конфигурировании загрузчика была указана маска:
$config['allowed_types']='gif|jpg|png|rar|zip|exe|txt';
и пользователь попытался загрузить *.exe файл. Почему так происходит? Ведь в маске указан тип "exe", и пользователь загружает *.exe-файл?
В рунете и у буржуев везде встречается такое объяснение: это происходит из-за того, что в файле APPLICATION/config/mimes.php, для типа exe (или для какого-нибудь другого типа, который мы хотим загрузить) не прописаны все возможные сигнатуры. Нужно дописать неизвестную сигнатуру, и загрузка заработает. В нашем случе, для расширения exe в файле mimes.php дана только одна сигнатура:
...
'exe' => 'application/octet-stream',
...
Для исправления, нужно прописать все возможные сигнатуры:
...
'exe' => array('application/octet-stream',
'application/x-msdownload',
'application/x-msdos-program'),
...
После чего, у некоторых счастливчиков начинает работать загрузка exe-файла.
Почему не у всех?
А потому, что загрузка еще и зависит от маски. При вот таких масках загрузка начнет работать:
'exe'
'exe|txt'
'txt|exe'
'zip|exe|txt'
'zip|exe|txt|gif'
'zip|txt|exe|gif|jpg'
А при таких масках загрузка работать всеравно не будет, и останется то же самое сообщение об ошибке:
'zip|rar|jpg|gif|txt|exe'
'gif|jpg|png|rar|zip|exe|txt'
'png|gif|rar|zip|exe|txt'
'gif|exe|txt'
'rar|png|exe'
Поняли в чем дело? Закономерность впринципе видна, но неоднозначна.
Все дело в баге, который появился в CodeIgniter, начиная с версии 1.7.1, и перетек в версию 1.7.2. Баг находится в классе CI_Upload, в методе is_allowed_filetype().
Посмотрим на этот код:
function is_allowed_filetype()
{
if (count($this->allowed_types) == 0 OR
!is_array($this->allowed_types))
{
$this->set_error('upload_no_file_types');
return FALSE;
}
$image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe');
foreach ($this->allowed_types as $val)
{
$mime = $this->mimes_types(strtolower($val));
// Images get some additional checks
if (in_array($val, $image_types))
{
if (getimagesize($this->file_temp) === FALSE)
return FALSE;
}
if (is_array($mime))
{
if (in_array($this->file_type, $mime, TRUE))
return TRUE;
}
else
{
if ($mime == $this->file_type)
return TRUE;
}
}
return FALSE;
}
Что ж мы видим? Мы видим дополнительную проверку сразу после комментария "// Images get some additional checks", которая срабатывает даже тогда, когда загружаемый файл не принадлежит ни к одному типу картинок. Функция getimagesize() будет обрабатывать exe-файл, когда в строке маски встретит какой-нибудь тип, соответсвующий картинке - gif, jpg, png. Если этот тип прописан до exe, метод is_allowed_filetype()вернет false.
Теперь стало понятно, в чем заключается баг. Будем его исправлять путем переопределения класса ядра.
Создадим в директории APPLICATION/libraries файл MY_Upload.php. Этот файл перекроет класс CI_Upload. В файле MY_Upload.php разместим следующий код:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
// Расширение класса CI_Upload, служащее для исправления бага CI,
// возникающего при проверке mime-типов
class MY_Upload extends CI_Upload
{
function MY_Upload($props = array())
{
parent::CI_Upload($props);
}
function is_allowed_filetype()
{
if (count($this->allowed_types) == 0 OR
! is_array($this->allowed_types))
{
$this->set_error('upload_no_file_types');
return FALSE;
}
$image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe');
foreach ($this->allowed_types as $val)
{
$mime = $this->mimes_types(strtolower($val));
// Если известные CI типы указаны в виде массива строк
if (is_array($mime))
{
if (in_array($this->file_type, $mime, TRUE))
{
// Если идет работа с изображением, нужна дополнительная проверка
if (in_array($val, $image_types))
return $this->image_additional_check($this->file_temp);
else
return TRUE;
}
}
else
{
// Иначе, известные CI типы указаны в виде строки
if ($mime == $this->file_type)
{
// Если идет работа с изображением, нужна дополнительная проверка
if (in_array($val, $image_types))
return $this->image_additional_check($this->file_temp);
else
return TRUE;
}
}
}
return FALSE;
}
private function image_additional_check($file_temp)
{
if (getimagesize($file_temp) === FALSE)
return FALSE;
else
return TRUE;
}
}
?>
И после этих действий баг должен исчезнуть.
Баг внесен в багтрекер CodeIgniter: http://codeigniter.com/bug_tracker/bug/13409, будет ли исправлен в следующих версиях - не знаю.
UPD: Через несколько месяцев после написания этой статьи вышел релиз CodeIgniter v.1.7.3, в котором наконец-то данный баг в классе CI_Upload был исправлен. Пользуйтесь версией 1.7.3 или более старшей.