Nickolay.info. PHP. Статьи. Загрузка файлов на сервер с помощью PHP

Загрузка файлов на сервер с помощью PHP

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

Сначала сформулируем требования к скрипту для загрузки файлов или картинок на PHP.

Как минимум, такой код должен уметь следующее:

Сначала все задаваемые пользователем настройки соберём в файл config.php:

<?php 
 define ("MAX_WIDTH","800");     //Макс. ширина картинки в пикселах
 define ("MAX_HEIGHT","800");    //Макс. высота картинки в пикселах
 define ("FOLDER","./uploads"); 
              //Путь к папке для картинок, БЕЗ слеша в конце, латинские буквы
 define ("MAX_SIZE","1048576");  //Максимальный размер файла, байт
 define ("USE_GDLIB","1"); 
  //Использовать библиотеку GDLib (php_gd2.dll) для масштабирования больших изображений
  //1 - включить, 0 - выключить
?>

Никаких особенных настроек нашему скрипту не понадобится. Если скрипт размещается на хостинге, на папку загрузок uploads достаточно прав 755 (про права говорится в этом месте сайта ), при размещении на локалхосте и локальном сервере Windows проще всего дать права 777 для гостевой учетной записи интернета и учётной записи для запуска IIS ( ссылка).

Следует также помнить, что для успешной загрузки файлов должны быть такие же права на временную папку PHP, узнать которую можно из настройки upload_tmp_dir файла php.ini (на хостинге обычно всё уже настроено и настройка пуста).

Так как нашему скрипту параметры будут передаваться только методом POST, для обработки параметров достаточно следующего кода (файл params.php):

<?php
 while (list($num,$var) = each($params)) {
  if (!empty($_POST[$var])) $$var = htmlspecialchars($_POST[$var]);
  else $$var = '';
 }
?>

Список разрешённых параметров будет задан в главном файле index.php, хотя уже ясно, что таковой понадобится только один - имя кнопки "Загрузить".

Напишем несколько полезных для поставленной задачи функций и включим их в файл functions.php.

Во-первых, это функция gdVersion для определения версии библиотеки gdLib (для надёжности потребуем версию не ниже 2, что соответствует PHP 5 версии).

Функция tumbmaker будет заниматься обработкой изображений, её заголовок таков:

function tumbmaker ($src, $dest, $width, $height, $rgb=0xFFFFFF, $quality=65)

Параметры $src и $dest - ссылки на исходный и целевой файл, $width и $height - требуемые ширина и высота рисунка в пикселах, параметр $rgb задаёт фоновый цвет нового изображения (по умолчанию принят белый), а параметр $quality определяет качество генерируемого изображения JPEG (по умолчанию 65%).

Эта функция преобразует размерности к тем, что мы указали. Чтобы вычислить "правильные" размерности и пропорционально сжать рисунок, напишем маленькую вспомогательную функцию get_new_size:

function get_new_size($width,$height,$max_width,$max_height)

Она пропорционально преобразует размеры $width и $height с учётом максимального прямоугольника, куда разрешено вписывать рисунок $max_width и $max_height.

Четвёртая функция - let_to_num - будет переводить размерности, хранящиеся в файле php.ini, в нормальное число байт - потому что 2 мегабайта, к примеру, там обозначены как 2M.

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

Вот что получилось (файл functions.php):

<?php
 require_once "config.php";
 function gdVersion() {
  $gdv=@gd_info();
  $ver=$gdv['GD Version'];
  $v=0;
  if (preg_match("/.*([0-9]+)\.([0-9]+)\.([0-9]+).*/", $ver, $r)) 
   $v=$r[1];//.".".$r[2].".".$r[3];
  return $v;
 }

 function get_new_size($width,$height,$max_width,$max_height) {
  $w=$width; $h=$height;
  $dw=$max_width/$width; $dh=$max_height/$height;
  if ($width>$max_width and $height>$max_height) {
   if ($dw<$dh) { $w=$max_width; $h=floor($height*$dw); }
   else { $h=$max_height; $w=floor($width*$dh); }  
  }
  else if ($width>$max_width and $height<=$max_height) {
   $w=$max_width; $h=floor($height*$dw);
  }
  else if ($width<=$max_width and $height>$max_height) {
   $h=$max_height; $w=floor($width*$dh);
  }
  return array ($w, $h);
 }

 function tumbmaker ($src, $dest, $width, $height, $rgb=0xFFFFFF, $quality=65) {
  if (!file_exists($src)) return false;
  $size = getimagesize($src);
  if ($size === false) return false;
  $format = strtolower(substr($size['mime'], strpos($size['mime'], '/')+1));
  $icfunc = "imagecreatefrom" . $format;
  if (!function_exists($icfunc)) return false;
  $x_ratio = $width / $size[0];
  $y_ratio = $height / $size[1];
  $ratio = min($x_ratio, $y_ratio);
  $use_x_ratio = ($x_ratio == $ratio);
  $new_width = $use_x_ratio ? $width : floor($size[0] * $ratio);
  $new_height = !$use_x_ratio ? $height : floor($size[1] * $ratio);
  $new_left = $use_x_ratio ? 0 : floor(($width - $new_width) / 2);
  $new_top = !$use_x_ratio ? 0 : floor(($height - $new_height) / 2);
  $isrc = $icfunc($src);
  $idest = imagecreatetruecolor($width, $height);
  imagefill($idest, 0, 0, $rgb);
  imagecopyresampled($idest, $isrc, $new_left, $new_top, 0, 0,
   $new_width, $new_height, $size[0], $size[1]);
  imagejpeg($idest, $dest, $quality);
  imagedestroy($isrc);
  imagedestroy($idest);
  return true;
 }

 function let_to_num($v){ //Размерности php.ini в байты
  $l = substr($v, -1);
  $ret = substr($v, 0, -1);
  switch(strtoupper($l)){
   case 'P': $ret *= 1024;
   case 'T': $ret *= 1024;
   case 'G': $ret *= 1024;
   case 'M': $ret *= 1024;
   case 'K': $ret *= 1024;
   break;
  }
  return $ret;
 }

 function cyr2lat ($text) {
 $cyr2lat_replacements = array (
  "А" => "a","Б" => "b","В" => "v","Г" => "g","Д" => "d",
  "Е" => "e","Ё" => "yo","Ж" => "dg","З" => "z","И" => "i",
  "Й" => "y","К" => "k","Л" => "l","М" => "m","Н" => "n",
  "О" => "o","П" => "p","Р" => "r","С" => "s","Т" => "t",
  "У" => "u","Ф" => "f","Х" => "h","Ц" => "ts","Ч" => "ch",
  "Ш" => "sh","Щ" => "csh","Ъ" => "","Ы" => "i","Ь" => "",
  "Э" => "e","Ю" => "yu","Я" => "ya",
  "а" => "a","б" => "b","в" => "v","г" => "g","д" => "d",
  "е" => "e","ё" => "yo","ж" => "dg","з" => "z","и" => "i",
  "й" => "y","к" => "k","л" => "l","м" => "m","н" => "n",
  "о" => "o","п" => "p","р" => "r","с" => "s","т" => "t",
  "у" => "u","ф" => "f","х" => "h","ц" => "ts","ч" => "ch",
  "ш" => "sh","щ" => "sch","ъ" => "","ы" => "i","ь" => "",
  "э" => "e","ю" => "yu","я" => "ya",
  "-" => "_"," " => "_"
 );
  return strtr ($text,$cyr2lat_replacements);
 }

?>

Осталось написать сам скрипт и поместить его в файл index.php. После формирования обычной титульной части html-файла:

<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
  <title>Сервис для загрузки файла</title>
 </head>
<body><div align="center">

подключим файл с функциями и наш обработчик параметров:

<?php
 require_once "functions.php";
 $params = array ('ok');
 require_once ("params.php");
?>

Форма для загрузки файла будет включать только соответствующий элемент HTML и кнопку "Загрузить":

<form action="index.php" method="post" enctype="multipart/form-data">
 <input type="file" name="url">
 <input type="submit" name="ok" value="Загрузить">
</form>

Атрибуты method="post" и enctype="multipart/form-data" в такой форме обязательны - иначе никакой загрузки не выйдет.

Вся остальная часть файла, если не считать закрывающих тегов в самом конце -

</div></body>
</html>

- будет состоять из одного блока PHP-кода, основные действия которого мы сейчас напишем.

Если форма была отправлена кнопкой "Загрузить", в параметрах должна присутствовать переменная $ok, если её нет - вернуться:

if (empty($ok)) return;

Далее установим служебные переменные - сообщение об ошибке $error, временное имя переданного файла $file, флаг загрузки файла $load, флаг $need_move для функции загрузки временного файла move_uploaded_file (дело в том, что при масштабировании рисунка он может быть загружен и функцией tumbmaker, тогда стандартная move_uploaded_file не нужна):

$error =  $file = ''; 
 $load = true;
 $need_move= true;

Всё остальное поместим в блок условия, проверяющего, что файл был передан:

if (isset($_FILES['url']) and !empty($_FILES['url']['tmp_name'])) {
  //здесь будет остальной код
 }
 else {
  $error .= '<br>Файл не передан.';
  if (!empty($_FILES['url']['error'])) $error .=' Код ошибки: '.$_FILES['url']['error'];
 }

Получаем временное имя файла и его размер в байтах, а также ограничение на размер - это минимальная из величин MAX_SIZE (наше собственное ограничение из конфигурации), post_max_size (настройка php.ini, определяющая максимальный разрешённый размер файла для передачи методом POST) и upload_max_filesize (стандартная настройка для максимального размера загружаемого файла).

Если размер превышен, формируем соответствующее сообщение:

$file = $_FILES['url']['tmp_name'];
  $file_size = $_FILES['url']['size'];
  $php_max_size = min(let_to_num(ini_get('post_max_size')),
   let_to_num(ini_get('upload_max_filesize')));
  $max_size=min(MAX_SIZE,$php_max_size);
  if ($file_size > $max_size) {
   $error .= '<br>Слишком большой по объёму файл. Ограничение в настройках '.
    ($max_size==MAX_SIZE?'скрипта (config.php)':
    'PHP (см. настройки post_max_size,upload_max_filesize)').
    ': '.$max_size.' байт';
   $load=false;
  }
  if ($_FILES['url']['error']!=UPLOAD_ERR_OK) {
   $error .= '<br>Сервер вернул ошибку закачки файла. Код ошибки: '.
    $_FILES['url']['error'];
   $load=false;
  }

Извлекаем тип переданного файла, формируем путь к записываемому файлу $filename, при этом имя файла, возможно, будет изменено методом cyr2lat:

$path_parts = pathinfo($_FILES['url']['name']);
  $filetype = strtolower($path_parts['extension']);
  $filename = FOLDER.'/'.cyr2lat(strtolower($path_parts['basename']));

По типу файла (не самый надёжный способ на самом деле) проверяем, загружается ли картинка. Для этого введём флаг $is_pic:

$is_pic = true;
  switch ($filetype) {
   case "jpeg":
   case "jpg": 
   case "gif": 
   case "png": break;
   default:  $is_pic=false;
  }

Если это картинка, проверяем её размер, при необходимости пытаемся сжать, если включена настройка USE_GDLIB и библиотека доступна. Если сжать рисунок удалось, ставим $need_move=false, чтобы не грузить рисунок повторно методом move_uploaded_file:

if ($is_pic) {
   $size = getimagesize($file);
   $width = $size[0];
   $height = $size[1];
   if ($width>MAX_WIDTH or $height>MAX_HEIGHT) {
    $ver = gdVersion();
    if (USE_GDLIB=='1') {
     if ($ver>1) {
      $new_size = get_new_size($width,$height,MAX_WIDTH,MAX_HEIGHT);
      $r=tumbmaker ($file,$filename,$new_size[0],$new_size[1]);
      if (!$r) {
        $error .= '<br>Не удалось программно уменьшить изображение. 
         Пожалуйста, проверьте работу библиотеки GBLib или загрузите 
         картинку меньшего размера. Установки из настроек сайта: 
         ширина до '.MAX_WIDTH.', высота до '.MAX_HEIGHT.' пикс.';
        $load=false;
       }
       else $need_move=false; 
      }
      else {
       $error .= '<br>В настройках сайта включено масштабирование 
        больших изображений, но библиотека GDLib недоступна. 
        Пожалуйста, настройте её или загрузите картинку меньшего размера. 
        Установки из настроек сайта: ширина до '.MAX_WIDTH.', высота до '.
        MAX_HEIGHT.' пикс.';
       $load=false;
      }
     }
     else {
      $error .= '<br>Превышен максимальный размер рисунка. Установки 
       из настроек сайта: ширина до '.MAX_WIDTH.', высота до '.MAX_HEIGHT.
       ' пикс. Автоматическое масштабирование изображений: выключено';
      $load=false;
     }
    }
   }

После всего этого, если установлены флаги $load и $need_move, можно загрузить картинку стандартным методом. После этого ставим на загруженную картинку права 644:

if ($load) {
   if ($need_move) {
    if (move_uploaded_file ($file,$filename)) {
     chmod ($filename,0644);
    }
    else {
     $error .= '<br>Не удалось закачать файл '.$filename.' из временного '.
     $file.'<br>Информация для отладки: ';
     print_r($_FILES);
    }
   }
  }

Теперь в конце основного блока кода можно проверить, удалён ли временный файл (по идее, move_uploaded_file удалит его сама после успешной загрузки), проверить, не нужно ли вывести сообщение об ошибке, если нет - дать ссылку на загруженный файл:

if (file_exists($file)) @unlink ($file);
 if (!empty($error)) {
  print 'Возникли проблемы!'.$error;
 }
 else {
  print 'Файл загружен, вот он: 
   <a href="'.$filename.'" target="_blank">'.$filename.'</a>';
 }

Вот что получилось у меня после проверки кода на хостинге в браузере Google Chrome:

Вывод примера на загрузку файла в PHP

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

 Код этого примера в архиве ZIP (4 Кб)

Возможна ли безопасная загрузка файлов

Отдельно нужно сказать несколько слов о безопасности загрузки файлов. Следует понимать, что любая форма для загрузки юзером файлов на ваш сайт потенциально опасна. Например, толковый юзер может загрузить shell-файл и получить доступ к вашему серверу. Чтобы такого не произошло, надо убрать права "остальным пользователям", то есть, поставить права 666 на папку, куда пользователи загружают файлы. По умолчанию там обычно права 755.

Но и этого может оказаться недостаточно. Я считаю, что файлы можно "складывать" на сервер как обычно, но под именем, полученным скриптом (функцией, uniqid, например), а настоящее имя файла (если оно нужно) и MIME-тип лучше хранить в базе. При этом "отдавать" файлы тоже лучше не "напрямую", а скриптом. Вот пример простого класса для поддержки скачивания файлов:

<?php
class download {

 var $properties = array( 
 'old_name' => "",
 'new_name' => "",
 'type' => "",
 'size' => "",
 'resume' => "",
 'max_speed' => ""
 );

 var $range = 0;

 function download($path, $name="", $resume=0, $max_speed=0) {
  $name = ($name == "") ? substr(strrchr("/".$path,"/"),1) : $name;
  $file_size = @filesize($path);
  $this->properties = array (
   'old_name' => $path,
   'new_name' => $name,
   'type'=> "application/force-download",
   'size' => $file_size,
   'resume' => $resume,
   'max_speed' => $max_speed
  );

  if ($this->properties['resume']) {
   if (isset($_SERVER['HTTP_RANGE'])) {
    $this->range = $_SERVER['HTTP_RANGE'];
    $this->range = str_replace("bytes=", "", $this->range);
    $this->range = str_replace("-", "", $this->range);
   } 
   else {
    $this->range = 0;
   }
   if ($this->range > $this->properties['size']) $this->range = 0;
  } 
  else {
   $this->range = 0;
  }
 }

 function download_file() {
  if ($this->range) { header($_SERVER['SERVER_PROTOCOL']." 206 Partial Content"); } 
  else { header($_SERVER['SERVER_PROTOCOL']." 200 OK"); }
  header("Pragma: public");
  header("Expires: 0");
  header("Cache-Control:");
  header("Cache-Control: public");
  header("Content-Description: File Transfer");
  header("Content-Type: ".$this->properties["type"]);
  header('Content-Disposition: attachment; filename="'.$this->properties["new_name"].'";');
  header("Content-Transfer-Encoding: binary");
  if ($this->properties['resume']) header("Accept-Ranges: bytes");
  if ($this->range) {
   header("Content-Range: bytes {$this->range}-".($this->properties['size']-1)."/".$this->properties['size']);
   header("Content-Length: ".($this->properties['size']-$this->range));
  } 
  else {
   header("Content-Length: ".$this->properties['size']);
  }
  @ini_set('max_execution_time', 0);
  @set_time_limit();
  $this->_download($this->properties["old_name"], $this->range);
 }

 function _download ($filename, $range=0) {
  @ob_end_clean();
  if (($speed = $this->properties['max_speed']) > 0) $sleep_time = (8 / $speed) * 1e6;
  else $sleep_time = 0;
  $handle = fopen($filename, 'rb');
  fseek($handle,$range);
  if ($handle === false) { return false; }
  while (!feof($handle)) {
   print (fread($handle, 1024*8));
   ob_flush();
   flush();
   usleep($sleep_time);
  }
  fclose($handle);
  return true;
 }
}
?>

Рейтинг@Mail.ru

вверх гостевая; E-mail