Idiot-proof, cross-browser force скачать в PHP

Я использую принудительную загрузку для загрузки в основном zips и mp3 на сайт, который я сделал ( http://pr1pad.kissyour.net ) – отслеживать загрузки в Google Analytics, в базе данных и скрывать реальный путь загрузки:

Это так:

extending CI model ... - bunch of code function _fullread ($sd, $len) { $ret = ''; $read = 0; while ($read < $len && ($buf = fread($sd, $len - $read))) { $read += strlen($buf); $ret .= $buf; } return $ret; } function download(){ /* DOWNLOAD ITSELF */ ini_set('memory_limit', '160M'); apache_setenv('no-gzip', '1'); ob_end_flush(); header("Pragma: public"); header("Expires: 0"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Cache-Control: public",FALSE); header("Content-Description: File Transfer"); header("Content-type: application/octet-stream"); if (isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false)) header('Content-Type: application/force-download'); //IE HEADER header("Accept-Ranges: bytes"); header("Content-Disposition: attachment; filename=\"" . basename("dir-with- files/".$filename) . "\";"); header("Content-Transfer-Encoding: binary"); header("Content-Length: " . filesize("dir-with-files/".$filename)); // Send file for download if ($stream = fopen("dir-with-files/$filename", 'rb')){ while(!feof($stream) && connection_status() == 0){ //reset time limit for big files set_time_limit(0); print($this->_fullread($stream,1024*16)); flush(); } fclose($stream); } } 

Это на LAMP с CI 1.7.2. Это мой собственный метод, составленный из разных практик по всему Интернету, потому что во время разработки эти проблемы возникли: – ограничение сервера . ini_set не помогло, поэтому я использовал буферизованный _fullread вместо обычного fread , который использовался insted из @readonly – ob_end_flush (), потому что сайт выполнен в CI1.7.2, и мне нужно очистить буфер

Теперь … Это не работает. Так оно и было, тогда оно перестало показывать ожидаемый размер / время загрузки – я пытался его очистить, а когда я очищал код, что-то случилось, я не знаю, что и в какой-либо предыдущей версии – он не работал (нет изменение настроек вообще) – edit : do not work = выводит все в окно браузера.

Поэтому я сказал, прикручу его, я посмотрю здесь.

Итак, я в основном ищу сценарий или функцию, которую я могу разместить в моей модели вывода и буду делать:

  • Call force-download (в Chrome start download, в IE, FF, Safari открывается модальное открытие / сохранение / отмена)
  • Покажите размер файла и приблизительное время dl (это зависит от браузера, я знаю, но сначала браузер должен знать размер файла
  • WORK (проверено и подтверждено!) В IE6,7,8, FF3, Opera, Chrome & and Safari на ПК + Mac (Linux … мне все равно) – это для части заголовка
  • на сервере у меня также есть что-то вроде ограничения на 56 МБ памяти, к которому я не могу добавить, так что это также важно

Заранее спасибо.

Редактирование : теперь я чувствую себя более прикрученным, чем когда-либо / раньше, так как я пытался принудительно загрузить с помощью .htaccess – пока он работал, у него было несколько незначительных / крупных (выбрать ваши) проблемы

  • он показал полный путь (второстепенный для меня)
  • он ждет завершения полной загрузки (показывается как «соединение»), а затем просто покажет, что она загружается, и загружается за одну секунду (майор для меня)

Теперь, хотя я удалил .htaccess, он все еще ждет завершения загрузки (как если бы он сначала загружался в кеш), а просто connected и отобразил диалог открытия / сохранения.

Итак, я использовал этот код (это модифицированная версия возобновляемого http-загрузки, найденная в Интернете)

 function _output_file($file, $path) { $size = filesize($path.$file); @ob_end_clean(); //turn off output buffering to decrease cpu usage // required for IE, otherwise Content-Disposition may be ignored if(ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off'); header('Content-Type: application/force-download'); header('Content-Disposition: attachment; filename="'.basename($file).'"'); header("Content-Transfer-Encoding: binary"); header('Accept-Ranges: bytes'); /* The three lines below basically make the download non-cacheable */ header("Cache-control: no-cache, pre-check=0, post-check=0"); header("Cache-control: private"); header('Pragma: private'); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // multipart-download and download resuming support if(isset($_SERVER['HTTP_RANGE'])) { list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2); list($range) = explode(",",$range,2); list($range, $range_end) = explode("-", $range); $range=intval($range); if(!$range_end) { $range_end=$size-1; } else { $range_end=intval($range_end); } $new_length = $range_end-$range+1; header("HTTP/1.1 206 Partial Content"); header("Content-Length: $new_length"); header("Content-Range: bytes $range-$range_end/$size"); } else { $new_length=$size; header("Content-Length: ".$size); } /* output the file itself */ $chunksize = 1*(1024*1024); //you may want to change this $bytes_send = 0; if ($file = fopen($path.$file, 'rb')) { if(isset($_SERVER['HTTP_RANGE'])) fseek($file, $range); while (!feof($file) && (!connection_aborted()) && ($bytes_send<$new_length) ) { $buffer = fread($file, $chunksize); print($buffer); //echo($buffer); // is also possible flush(); $bytes_send += strlen($buffer); } fclose($file); } else die('Error - can not open file.'); die(); } 

а затем в модели:

 function download_file($filename){ /* DOWNLOAD */ $path = "datadirwithmyfiles/"; //directory //track analytics include('includes/Galvanize.php'); //great plugin $GA = new Galvanize('UA-XXXXXXX-7'); $GA->trackPageView(); $this->_output_file($filename, $path); } в function download_file($filename){ /* DOWNLOAD */ $path = "datadirwithmyfiles/"; //directory //track analytics include('includes/Galvanize.php'); //great plugin $GA = new Galvanize('UA-XXXXXXX-7'); $GA->trackPageView(); $this->_output_file($filename, $path); } 

Он работает так, как ожидалось, во всех браузерах упоминания на Win / MAC – до сих пор никаких проблем с ним.

Хорошо, это старый вопрос, и Адам уже принял свой собственный ответ, поэтому, по-видимому, он получил это для себя, но он не объяснил, почему это сработало. Одна вещь, которую я заметил, заключалась в том, что он использовал заголовки:

 header("Pragma: public"); header("Cache-Control: public",FALSE); 

Принимая во внимание, что в решении он использовал:

 header("Cache-control: private"); header('Pragma: private'); 

Он не объяснил, почему он изменил их, но я подозреваю, что это связано с использованием SSL. Недавно я решил аналогичную проблему в программном обеспечении, которое должно включать загрузку по HTTP и HTTPS, используя следующее, чтобы добавить правильный заголовок:

 if(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) { header("Cache-control: private"); header('Pragma: private'); } else { header('Pragma: public'); } 

Надеюсь, кто-то найдет информацию в этом ответе полезным дополнением к вышеизложенному.

Есть одна вещь, которую я нахожу странной: вы вызываете ob_end_flush() в начале функции. Это фактически очищает выходной буфер, но также выводит все на клиента сначала (я предполагаю, включая Content-Headers, установленный CodeIgniter). Измените вызов на ob_end_clean() , он очистит буфер и отменит его. Это даст вам чистый старт для создания собственных заголовков.

Еще один совет:

Вместо того, чтобы читать файл как поток и передавать его по-блочно, вы можете попробовать эту функцию:

 // ... if (file_exists("dir-with-files/$filename")) { readfile($file); } 

Это почти все.

print($this->_fullread($stream,1024*16));

Я предполагаю, что _fullread находится внутри класса? Если код выглядит так, то $this-> не работает.

Выводит ли содержимое файла на экран, если вы закомментировали весь материал заголовка?

Просто выстрел в темноте … каждый заголовок, который я отправляю в свой код загрузки силы (который не так хорошо протестирован как ваш), такой же, как ваш, за исключением того, что я вызываю: заголовок («Cache-Control: private») ,ложный);

вместо: header («Cache-Control: public», FALSE);

Я не знаю, поможет ли это или нет.

Если вы собираетесь использовать этот метод «Echo it out with php», то вы не сможете показать оставшееся время или ожидаемый размер для ваших пользователей. Зачем? Поскольку, если браузер пытается возобновить загрузку в середине, у вас нет способа обработать этот случай в PHP.

Если у вас есть обычная загрузка файлов, Apache может поддерживать возобновленные загрузки по HTTP, но в случае приостановки загрузки Apache не имеет никакого способа выяснить, где в вашем скрипте выполнялись вещи, когда клиент запрашивает следующий фрагмент.

По сути, когда браузер приостанавливает загрузку, он полностью завершит подключение к веб-серверу. Когда вы возобновляете загрузку, соединение снова открывается, и запрос содержит флаг «Начать с байта X». Но для веб-сервера, который смотрит на ваш PHP выше, откуда берется X?

Теоретически, возможно, серверу удастся определить, где возобновить ваш скрипт в случае прерывания загрузки, Apache не пытается выяснить, где его возобновить. В результате заголовок, отправленный в браузер, указывает, что сервер не поддерживает возобновление, что отключает ожидаемые элементы размера и времени в большинстве основных браузеров.

EDIT: Кажется, вы могли бы справиться с этим делом, но он будет принимать много кода с вашей стороны. См. http://www.php.net/manual/en/function.fread.php#84115 .

Вместо того, чтобы пытаться скрыть Ваш путь загрузки из мира, сделать его недоступным извне и получать доступ только к файлам с указанным выше скриптом. для этого вы помещаете файл htaccess (текстовый файл с именем «.htaccess» не забывайте о ведущей точке) в каталоге. Содержание htaccess будет следующим:

 order deny,allow deny from all allow from localhost 

Теперь, пытаясь получить доступ к пути из * world, веб-сервер сделает 401 запрещенным.

Безопасность через неясность – это не то, что вы хотите.