Создание одноразовых URL-адресов

Создание одноразовых URL-адресов

От автора: при разработке веб-приложений иногда возникает задача создания одноразовых ссылок для пользователей. То есть ссылок, которые можно использовать только один раз. Такие ссылки можно использовать как для скачивания различного материала на Вашем сайте, так и для активации нового зарегистрированного пользователя. Поэтому в данном уроке мы с Вами рассмотрим создание одноразовых ссылок на примере ссылки для скачивания файла.

скачать исходникискачать урок

1. Постановка задачи

Для начала давайте условимся, что одноразовую ссылку мы будем создавать для скачивания файла. Данный файл будем хранить в папке file и это обычное изображение, которое необходимо для примера.

Далее, создание одноразовой ссылки заключается в генерации уникальной строки, которую необходимо передать в специальный файл обработчик. Данный файл выполнит необходимые проверки и если ссылка верна (уникальная строка), значит, откроется доступ для скачивания файла. Конечно, для каждого пользователя необходимо создавать отдельную ссылку, и поэтому все созданные ссылки необходимо где то хранить. В качестве хранилища ссылок можно выбрать или базу данных, или обычный текстовый файл. Мы в данном уроке будем использовать текстовый файл для хранения созданных ссылок.

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

2. Создание одноразовых ссылок

Итак, на локальном компьютере в папке lessons, я создал папку onetime, в которой мы будем хранить наш будущий скрипт. То есть путь к папке с скриптом будет следующий: http://localhost/lessons/onetime/

Теперь, давайте создадим новый файл, под названием get_hash.php, который будет генерировать уникальную строку и тем самым формировать одноразовую ссылку, для скачивания файла. Первым делом, генерируем уникальную строку:

$hash = md5(microtime());

Для этого используем функцию microtime(), которая возвращает текущую метку времени. То есть в каждый момент времени мы будем получать разное значение. Напомню, что возвращаемое значение это строка вида: «msec sec», где sec представляет собой текущее время, прошедшее с начала Эпохи Unix (1 января 1970 0:00:00 GMT) в секундах, а msec — это количество микросекунд, прошедших после sec. Затем обработаем полученное значение функцией md5(), то есть зашифруем полученную строку однонаправленным алгоритмом шифрования md5 (однонаправленное – значит, что данные можно зашифровать, но нельзя расшифровать). Давайте выведем на экран, значение переменной $hash:

Как Вы видите, строка успешно генерируется. Далее, создадим переменную $file, в которой будем хранить имя файла, в котором будут храниться созданные уникальные строки для ссылок:

$file = "get_url.txt";

Теперь, созданную строку необходимо сохранить в файле, поэтому, давайте откроем текстовый файл для записи:

$fd = fopen($file,"a");
if(!$fd) {
	exit("Не возможно открыть файл");
}

Для открытия файла для записи используем функцию fopen(). Которая открывает файл в определенном режиме. Мы будем работать в режиме a, то есть, открываем файл только для записи и указатель перемещаем в конец файла (если мы вызываем функцию в первый раз, то открываемый файл будет создан).

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

if(!flock($fd,LOCK_EX)) {
	exit("Блокировка файла не удалась");
}

Напомню, что данная функция накладывает блокировку на файл. Режим работы которой, задается константами, передаваемыми вторым параметром к данной функции. Режим LOCK_EX – накладывает эксклюзивную блокировку (запись). И при этом все возможные обращения файлу будут запрещены, то есть пока он заблокирован. Кроме записи данных в файл из нашего скрипта, что собственно мы и выполним:

fwrite($fd,$hash."\n");

Для записи мы используем функцию fwrite, и записываем сгенерированную уникальную строку. Теперь давайте разблокируем файл, то есть, разрешим обращение к нему из вне, и закроем файл:

if(!flock($fd,LOCK_UN)) {
	exit("Не возможно разблокировать файл");
}
fclose($fd);

Для разблокировки доступа к файлу, мы используем функцию flock(), и передаем константу LOCK_UN, то есть снятие блокировок. Теперь, давайте несколько раз обновим скрипт и посмотрим, что записалось в файл:

Как Вы видите, данные в файл успешно записаны. Теперь необходимо сформировать ссылку и отобразить ее на экране для пользователя:

$path = substr($_SERVER['PHP_SELF'],0,strrpos($_SERVER['PHP_SELF'],"/"));
echo "<p>Ваша ссылка для скачивания</p>";
echo "<a href='http://".$_SERVER['HTTP_HOST'].$path."/get_file.php?hash=".$hash."'>http://".$_SERVER['HTTP_HOST'].$path."/get_file.php?hash=".$hash."</a>";

Для этого нам необходимо определить папку, в которой содержится скрипт. Поэтому, используя функцию substr, мы получаем имя папки и сохраняем в переменной $path. Затем выводим на экран ссылку, используя ячейку суперглобального массива $_SERVER['HTTP_HOST'] и полученную переменную $path. Так же добавляем имя файла обработчика, в нашем случае это get_file.php и, используя GET параметры, передаем сгенерированную строку. Теперь давайте перейдем в браузер и посмотрим, что у нас получилось:

Как Вы видите, ссылка выводится, уникальная строка генерируется и записывается в текстовый файл. Значит, мы все сделали правильно. Теперь на всякий случай приведу полный код файла, get_hash.php:

<?php

$hash = md5(microtime());

$file = "get_url.txt";

$fd = fopen($file,"a");
if(!$fd) {
	exit("Не возможно открыть файл");
}
if(!flock($fd,LOCK_EX)) {
	exit("Блокировка файла не удалась");
}
fwrite($fd,$hash."\n");

if(!flock($fd,LOCK_UN)) {
	exit("Не возможно разблокировать файл");
}
fclose($fd);

$path = substr($_SERVER['PHP_SELF'],0,strrpos($_SERVER['PHP_SELF'],"/"));
echo "<p>Ваша ссылка для скачивания</p>";
echo "<a href='http://".$_SERVER['HTTP_HOST'].$path."/get_file.php?hash=".$hash."'>http://".$_SERVER['HTTP_HOST'].$path."/get_file.php?hash=".$hash."</a>";
?>

Теперь необходимо создать скрипт, который получит передаваемые данные и выполнит необходимые проверки.

3. Проверка ссылок

Итак, создаем пустой файл get_file.php, в котором мы будем проверять правильность ссылки и если все верно, предоставлять доступ для скачивания файла. Первым делом, создаем две переменные, которые будут содержать имя файла для скачивания (ссылку на который мы создаем), и имя файла с уникальными строками ссылок:

$s_file = "file/Desert.jpg";
$file = "get_url.txt";

Затем, создаем переменную, которая будет содержать разрешение на скачивание файла. То есть, если значение данной переменной равно TRUE, значит, необходимо разрешить скачивание ссылки:

$check = FALSE;

Данный в этот файл передаются методом GET (через адресную строку), значит в суперглобальном массиве GET, будет создана ячейка hash. Поэтому, сохраним значение данной ячейки, так же в отдельной переменной:

$hash = $_GET['hash'];

Теперь, так как мы знаем, что уникальная строка, получена с помощью шифрования md5, значит уникальная строка должна содержать ровно 32 символа. Поэтому, давайте выполним первую проверку:

if(strlen($hash) != 32) {
	exit("Не правильныая ссылка");
}

Теперь считаем файл с уникальными строками и все его содержимое построчно, сохраним в ячейках массива:

$arr = file($file);

То есть массив $arr, содержит в каждой своей ячейке сгенерированную строку из файла. К примеру, если распечатать его функцией print_r(), мы увидим следующее:

Теперь, так как все содержимое файла, содержится в одном массиве, мы можем проверить, есть ли строка, переданная через GET параметры (значение переменной $hash) в одной из ячеек массива $arr. И если действительно есть совпадение, значит, доступ к файлу можно открыть.

Но, нам ведь еще нужно обеспечить одноразовое использование каждой ссылки. Поэтому, мы очистим файл с уникальными строками. Затем в цикле обойдем массив $arr и на каждой итерации цикла будем сравнивать текущее значение ячейки массива, с значением переменной $hash. Если совпадение не найдено, значение данной ячейки запишем обратно в файл, если же найдено совпадение, то записывать ничего не будем, только в переменную $check сохраним значение TRUE. При этом мы обеспечим одноразовое использование ссылки. Так как если ссылка пройдет проверку (будет найдено совпадение с кодом файла), ее уникальный код при перезаписи уже не попадет в текстовый файл. И повторно использовать данную ссылку (уникальный код для проверки) будет не возможно.

Поэтому, открываем файл для записи в режиме w (открывает файл только для записи и помещает указатель в начало файла и обрезает файл до нулевой длины.):

$fd = fopen($file,"w");
if(!$fd) {
	exit("Не возможно открыть файл");
}

Затем, блокируем файл, от доступа из вне, пока идет запись:

if(!flock($fd,LOCK_EX)) {
	exit("Блокировка файла не удалась");
}

Формируем цикл, который проверит и перезапишет уникальные коды в файл, где не найдено совпадений с значением переменной $hash:

for ($i = 0; count($arr) > $i; $i++) {
	
	if($hash == rtrim($arr[$i])) {
		
		$check = TRUE;
	}
	else {
		fwrite($fd,$arr[$i]);
	}
}

Далее, снимаем блокировки и закрываем открытый файл:

if(!flock($fd,LOCK_UN)) {
	exit("Не возможно разблокировать файл");
}
fclose($fd);

Теперь осталось только проверить, что содержится в переменной $check, и если в ней содержится TRUE, значит можно отдать файл на скачивание. К примеру, можно поступить следующим образом:

if($check) {
	header("Content-Description: File Transfer");
	header("Content-Type: image/jpeg");
	header("Content-Disposition: attachment; filename=".basename($s_file));
	header("Content-Transfer-Encoding:binary");
	header("Content-Length: ".filesize($s_file));
	
	ob_clean();
	flush();
	
	readfile($s_file);
	exit();
}
else {
	exit("Не правильная ссылка!!!");
}

То есть, проверяем значение переменной $check, и если TRUE, то мы отправим несколько заголовков в браузер. Тем самым сообщая ему, что тип контента – это изображение формата jpeg (header(«Content-Type: image/jpeg»);). Так же, что изображение нужно не отобразить на экране, а передать для скачивания, то есть, что бы браузер открыл окно скачивания файла (header(«Content-Disposition: attachment; filename=».basename($s_file));). Ну и конечно же передаем имя файла и его размер (filename=».basename($s_file)); header(«Content-Length: «.filesize($s_file));).

Далее очищаем буфер обмена и читаем файл с помощью функции readfile(). И не забываем выйти из скрипта после прочтения файла. Если же в переменной $check содержится иное значение от TRUE, значит проверка не удалась и необходимо запретить доступ к скачиваемому файлу. Теперь на всякий случай приведу полный код файла get_file.php:

<?php
$s_file = "file/Desert.jpg";
$file = "get_url.txt";

$check = FALSE;
$hash = $_GET['hash'];

if(strlen($hash) != 32) {
	exit("Не правильныая ссылка");
}

$arr = file($file);

$fd = fopen($file,"w");
if(!$fd) {
	exit("Не возможно открыть файл");
}

if(!flock($fd,LOCK_EX)) {
	exit("Блокировка файла не удалась");
}

for ($i = 0; count($arr) > $i; $i++) {
	
	if($hash == rtrim($arr[$i])) {
		
		$check = TRUE;
	}
	else {
		fwrite($fd,$arr[$i]);
	}
}

if(!flock($fd,LOCK_UN)) {
	exit("Не возможно разблокировать файл");
}
fclose($fd);

if($check) {
	header("Content-Description: File Transfer");
	header("Content-Type: image/jpeg");
	header("Content-Disposition: attachment; filename=".basename($s_file));
	header("Content-Transfer-Encoding:binary");
	
	header("Content-Length: ".filesize($s_file));
	
	ob_clean();
	flush();
	
	readfile($s_file);
	exit();
}
else {
	exit("Не правильная ссылка!!!");
}
?>

Вот мы с Вами и реализовали механизм создания одноразовых ссылок. Надеюсь данный урок будет Вам полезен. А сейчас давайте прощаться.

Всего Вам доброго и удачного кодирования!

Фреймворк YII2: теория и первая практика

Овладейте азами фреймворка Yii2 за 5 дней!

Получить

Метки: ,

Комментарии Вконтакте:

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

Комментарии (3)

  1. Сергей

    Здравствуйте, Виктор! Сделал все по Вашему уроку и говорю Вам огромное спасибо. Если можно, то напишите пожалуйста еще пару моментов в дополнение…
    Как в файл get_url.txt получить расширенные данные? Конкретно требуется дата скачивания и ip, с которого скачали файл.
    Может даже напишите отдельную статью по этому вопросу?
    С благодарностью!

  2. Вячеслав

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

    Я к тому что надо сделать хоть минимальную степень защиты.
    Допустим сессии,ограничение по времени,запись куков,автоматическое начало закачки файла.

  3. Alexey

    Как сделать чтобы передовались на скачивая zip архивы или торренты, а не перенаправляло по адресу к файлу, так как папка и файлы не будут доступны.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree