Ярлыки

.htaccess (4) тестирование (8) шаблоны проектирования (3) css (5) Debian (6) docker (2) Doctrine2 (6) Git (6) html (4) java (6) javascript (13) jquery (11) LFS (3) linux (23) mac os (4) mod_rewrite (2) MSSQL (4) MySQL (18) ORM Doctrine (17) patterns (3) PDO (3) perl (7) PHP (64) PHPUnit (8) Python (15) SEO (2) Silex (1) SimpleXML (1) SQL (14) ssh (4) Ubuntu (24) Yii1 (1) Zend Framework (19) ZendFramework2 (8)

пятница, 6 апреля 2012 г.

Apache. PHP. Контролируемое скачивание файлов.

Задача такая, файлы лежат вне корневой директории сервера. Дать возможность скачать файл можно при определенных условиях, например, только авторизованным. Или в зависимости от оплаты аккаунта и тд. Делается это просто, с использованием XSendfile.
Во-первых можно просто послать заголовки
$file = 'path/to/file';
header("X-Sendfile: $file");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
Во-вторых для Zend Framework нашел такой хелпер контроллера
/**
 * X-Sendfile controller helper
 *
 * @copyright Copyright (c) 2008-2009 Pro Soft Resources USA Inc. 
 * (http://www.prosoftpeople.com)
 * @author    Rolando Espinoza La fuente (rho@prosoftpeople.com)
 * @license   http://www.opensource.org/licenses/bsd-license.php 
 * New BSD License
 * @version   $Id$
 */ 

class Zend_Controller_Action_Helper_Xsendfile 
    extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * @var bool Use Nginx specific header
     */
    protected $_isNginx = false;

    /**
     * @var bool Wheter to force download. Default true.
     */
    protected $_forceDownload = true;

    /**
     * Send files through X-Sendfile feature
     * 
     * @param  string $path     File path.
     * @param  string $filename File name. Optional.
     * @return $this
     */
    public function xsendfile($path, $mime = null, $filename = null)
    {
        // set filename if not given
        if (null === $filename) {
            $filename = basename($path);
        }

        $response = $this->getResponse();

        // send mime info
        if (null !== $mime) {
            $response->setHeader('Content-Type', $mime);
        }

        // send file as attachment
        if ($this->forceDownload()) {
            $contentDisposition = 'attachment; filename=' . $filename;
        } else {
            $contentDisposition = 'inline; filename=' . $filename;
        }

        // if server is Nginx use different header
        if ($this->isNginx()) {
            $headerName = 'X-Accel-Redirect';
        } else {
            $headerName = 'X-Sendfile';
        }

        // set response headers
        $response->setHeader('Content-Disposition', $contentDisposition)
                 ->setHeader($headerName, $path);

        // Done. Webserver will send the file 

        return $this;
    }

    /**
     * Force download flag
     * 
     * @param  boolean  Optional. 
     * @return boolean
     */
    public function forceDownload($flag = null)
    {
        if ($flag !== null) {
            $this->_forceDownload = (bool) $flag;
        }

        return $this->_forceDownload;
    }

    /**
     * Nginx web server flag
     *
     * TODO: Support internal paths
     * 
     * @param  boolean  Optional. 
     * @return boolean
     */
    public function isNginx($flag = null)
    {
        if ($flag !== null) {
            $this->_isNginx = (bool) $flag;
        }

        return $this->_isNginx;
    }

    /**
     * Direct pattern
     * 
     * @param  string $path     File path.
     * @param  string $filename File name. Optional.
     * @return $this
    */
    public function direct($path, $mime = null, $filename = null)
    {
        return $this->xsendfile($path, $mime, $filename);
    }
}
Пример использования в контроллере
public function downloadAction() {
    // disable output
    $this->_helper->layout->disableLayout();
    $this->_helper->viewRenderer->setNoRender(TRUE);

    $fileId = $this->_request->getParam('fid');
    $fileObj = $this->_helper->doctrineEntityManager
                    ->getRepository('File')
      ->findOneBy(array('id' => $fileId));
    // Проверяем права на скачивание файла
    if ($this->user->getStatus() === 'free') {
        if ($fileObj->getStatus() === 'pro')
            die('Permission denied');
    }
    // if using nginx
    //$this->_helper->Xsendfile->isNginx(true);
    // not force download
    //$this->_helper->Xsendfile->forceDownload(false);
    // send file with custom name
    //$this->_helper->xsendfile($file, 'application/pdf', 'report.pdf');

    $this->_helper->xsendfile($fileObj->getFilePath(),
        $fileObj->getFileMime());
}
Для того чтобы все это работало должен быть установлен модуль apache XSendfile (иначе будете скачивать файлы весом 0 байт):
root@host:~# apt-cache search xsendfile
libapache2-mod-xsendfile - Serve large static files efficiently from web applications
root@host:~# apt-get install libapache2-mod-xsendfile
После того как модуль установлен, проверяем его наличие в разрешенных модулях apache
root@host:~# ls /etc/apache2/mods-enabled/
...
xsendfile.load
...
И если его там нет создаем ссылку на загрузчик модуля в /etc/apache2/mods-avaliable. И в debian squeeze и в ubuntu 12.04 он прописался там после установки. Далее нам нужно включить модуль в .htaccess нашего сайта. Тут для debian и ubuntu есть различия, все дело в разных версиях модуля в репозиториях. Для Ubuntu .htaccess:
...
# enable xsendfile
XSendFile On
...
А в конфигурации хоста надо добавить следующую директиву, она разрешает загружать файлы из указанной директории, которая находится за пределами корня веб-сервера (корень /var/www/mysite/public)
leon@Berta:~$ cat /etc/apache2/sites-enabled/mysite.conf 
XSendFilePath /var/www/mysite/uploads

...
Для debian squeeze просто добавляем в .htacess две директивы
# enable xsendfile
XSendFile On

XSendFileAllowAbove on
Без этих двух вариантов директив XSendFileAllowAbove и XSendFile вы будете получать различные ошибки, или закачку файлов длинной 0 байт. Ошибки сервера смотрим
cat /var/log/apache2/error.log
К слову, директива XSendFileAllowAbove устарела и в новой версии модуля ее нет.

Комментариев нет:

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