Всё, вопрос решился.
Ранее предложенная конструкция не дает возможности скачивать файл. Точнее файл то скачивается, но в саму страницу, что соответствует принципу безопасности "каждый сайт в своей песочнице". Поэтому можно прибегнуть к хитрости перехода по ссылке.
А именно:
1. Клиент делает запрос на скачивание путем нажатия на кнопку и запуска скрипта.
2. XHR POST-запросом (ранее GET-запросом) отправляет имя файла на /download.php
3. download.php принимает запрос и формирует сессию, которая автоматически заносится в Сookies
- на случай отключенных cookies, через тот же xhr отвечает id сессии, а скрипт сам решит как использовать ответ
4. После ответа xhr скрипт перенаправляет на /download.php, где проверяется сессия и отправляется запрошенный файл
5. Получаемый файл теперь не считается браузером частью страницы, как это было ранее, и он начинает его загрузку
Стартовая страница (index.php)
<!DOCTYPE html>
<html>
<head>
<meta charset="windows-1251">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" type="txt/css" href="data/css.css">
<script src="data/js.js"></script>
</head>
<body>
<div id ="buttons">
<?
$dir = opendir ("files");
while ($file = readdir($dir)) // Ищем файлы в нужной директории
{
if(!($file=="."||$file==".."))
echo "<p>$file</p>";
}
?>
</div>
</body>
</html>
Обработка загрузок (download.php)
<?
if(isset($_GET[session_name()])){
session_id($_GET[session_name()]);
/* Если выключены cookie, то придет GET-запрос с нужной сессией */
}
session_start(); // Запускаем сессию
if ( isset($_POST['file']) ){
$file = pathinfo($_POST['file']);
if ($file['dirname'] != '.') exit ('Error'); // Не даем возможности гулять по директориям
$_SESSION["file_for_download"] = iconv( "utf-8", "cp1251", $file['basename']);
/* iconv() здесь позволяет работать с файлами на кириллице */
exit("?".session_name().'='.session_id()); // Отправляем данные сессии, вдруг cookie не работают
}
if ( isset($_SESSION["file_for_download"]) ){
$file = $_SESSION["file_for_download"];
unset($_SESSION["file_for_download"]);
/* Удаляем сессионную переменную,
чтобы при следующем входе в download.php не началась очередная загрузка */
if(file_exists("files/".$file)){
header('Content-Type: application/octet-stream');
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename=' . $file);
header('Content-Length: ' . filesize('files/' . $file));
/* Эти заголовки исчерпывающие в данной задаче */
readfile('files/' . $file . ''); // Отправка файла чтением
exit();
}
else
{
// Действия, если файла нет. Например, отправка файла с ошибкой и извинениями.
exit ("Неправильный файл: " . $file);
}
}
header('Location: /');
/* Если ничего из сценария не подошло, отправляем клиента на главную страницу (вдруг он не туда вошел) */
?>
Javascript (js.js)
function open_event(){
document.getElementById('buttons').onclick = download;
}
document.addEventListener("DOMContentLoaded", open_event);
function download (button_file){
name_file = button_file.target.innerHTML
var xhr = new XMLHttpRequest();
xhr.open('POST', 'download.php', true);
xhr.overrideMimeType('text/plain; charset=utf-8');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.status != 200) {
// Обработать ошибку
alert('Не 200: ' + xhr.status + ': ' + xhr.statusText ); // Например, 404: Not Found
} else {
if (xhr.readyState == 4){
if (xhr.responseText != "Error"){ // Обработка ошибки. У нас кто-то хочет полазить по директориям.
no_cookie = "";
if (!navigator.cookieEnabled) {no_cookie = xhr.responseText}; // Если Cookie отключены
window.location.href = "download.php" + no_cookie; // держим сессию через GET
/* Это не нужно если в PHP включена настройка session.use_trans_sid.
Также, при включенной настройке, не понадобится
isset($_GET[session_name()]) в download.php */
} else {
alert("Ошибка");
}
}
}
}
xhr.send('file='+name_file);
}
Полезные статьи:
Создание простой системы авторизации - зачем используют сессии
Подробное описание работы и объяснение механизма сессииОтдаем файлы эффективно с помощью PHPНовые возможности XMLHttpRequest2 - не совсем то, но интересно
Полезные ответы:
Проблема с русскими символами в имени файла
Полезные ресурсы:
О XMLHTTPRequestО php (есть переводы на русский)
Хорошо описаны заголовки HTTP
Статьи не в тему но интересно:
Определение кодировки текста в PHP