Я ищу метод, который кодирует строку в самый короткий возможная длина, и пусть это будет декодируемыми (чистый PHP, без SQL). У меня есть рабочий скрипт, но я недоволен длиной закодированной строки.
СЦЕНАРИЙ:
Ссылка на изображение (зависит от разрешения файла, которое я хочу показать пользователю):
Закодированная ссылка (поэтому пользователь не может угадать, как получить увеличенное изображение):
Итак, я бы хотел закодировать только часть поискового запроса в URL:
Метод, который я использую сейчас, закодирует приведенную выше строку запроса в:
Метод, который я использую:
$raw_query_string = 'img=/dir/dir/hi-res-img.jpg&w=700&h=500';
$encoded_query_string = base64_encode(gzdeflate($raw_query_string));
$decoded_query_string = gzinflate(base64_decode($encoded_query_string));
Как мне сократить закодированный результат и при этом иметь возможность декодировать его, используя только PHP?
Я подозреваю, что вам нужно больше думать о вашем методе хеширования, если вы не хотите, чтобы пользователь расшифровывал его. Вопрос с base64
это строка base64 выглядит как строка base64. Есть большая вероятность, что кто-то, кто достаточно сообразителен, чтобы посмотреть на источник вашей страницы, вероятно, узнает это тоже.
Первая часть:
метод, который кодирует строку для максимально короткой длины
Если вы гибки в своем URL-адресе или символах, это будет хорошей отправной точкой. Так как gzip получает много преимуществ, используя обратные ссылки, нет особого смысла, так как строка такая короткая.
Рассмотрим ваш пример — вы сохранили только 2 байта в сжатии, которые снова теряются при заполнении base64:
Non-сжат GZIP: string(52) "aW1nPS9kaXIvZGlyL2hpLXJlcy1pbWcuanBnJnc9NzAwJmg9NTAw"
сжат GZIP: string(52) "y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA=="
Если вы уменьшите размер вокаба, это, естественно, позволит вам лучше сжать. Допустим, мы удалили некоторую избыточную информацию
Взгляните на функции:
function compress($input, $ascii_offset = 38){
$input = strtoupper($input);
$output = '';
//We can try for a 4:3 (8:6) compression (roughly), 24 bits for 4 chars
foreach(str_split($input, 4) as $chunk) {
$chunk = str_pad($chunk, 4, '=');
$int_24 = 0;
for($i=0; $i<4; $i++){
//Shift the output to the left 6 bits
$int_24 <<= 6;
//Add the next 6 bits
//Discard the leading ascii chars, i.e make
$int_24 |= (ord($chunk[$i]) - $ascii_offset) & 0b111111;
}
//Here we take the 4 sets of 6 apart in 3 sets of 8
for($i=0; $i<3; $i++) {
$output = pack('C', $int_24) . $output;
$int_24 >>= 8;
}
}
return $output;
}
А также
function decompress($input, $ascii_offset = 38) {
$output = '';
foreach(str_split($input, 3) as $chunk) {
//Reassemble the 24 bit ints from 3 bytes
$int_24 = 0;
foreach(unpack('C*', $chunk) as $char) {
$int_24 <<= 8;
$int_24 |= $char & 0b11111111;
}
//Expand the 24 bits to 4 sets of 6, and take their character values
for($i = 0; $i < 4; $i++) {
$output = chr($ascii_offset + ($int_24 & 0b111111)) . $output;
$int_24 >>= 6;
}
}
//Make lowercase again and trim off the padding.
return strtolower(rtrim($output, '='));
}
В основном происходит удаление избыточной информации с последующим сжатием 4 байтов в 3. Это достигается благодаря эффективному наличию 6-битного подмножества таблицы ascii. Это окно перемещено так, что смещение начинается с полезных символов и включает в себя все символы, которые вы используете в данный момент.
Со смещением, которое я использовал, вы можете использовать что угодно от ASCII 38 до 102. Это дает вам результирующую строку 30 байтов, это 9-байтовое (24%) сжатие! К сожалению, вам нужно сделать его URL-безопасным (возможно, с base64), что возвращает его до 40 байт.
Я думаю, что в этот момент вы вполне можете предположить, что достигли уровня «безопасность через неизвестность», необходимого для остановки 99,9% людей. Давайте продолжим, однако, ко второй части вашего вопроса
поэтому пользователь не может угадать, как получить увеличенное изображение
Можно утверждать, что это уже решено с вышеупомянутым, но что вам нужно сделать, это передать это через секрет на сервере, предпочтительно с php openssl. Следующий код показывает полный поток использования функций выше и шифрование:
$method = 'AES-256-CBC';
$secret = base64_decode('tvFD4Vl6Pu2CmqdKYOhIkEQ8ZO4XA4D8CLowBpLSCvA=');
$iv = base64_decode('AVoIW0Zs2YY2zFm5fazLfg==');
$input = 'img=/dir/dir/hi-res-img.jpg&w=700&h=500';
var_dump($input);
$compressed = compress($input);
var_dump($compressed);
$encrypted = openssl_encrypt($compressed, $method, $secret, false, $iv);
var_dump($encrypted);
$decrypted = openssl_decrypt($encrypted, $method, $secret, false, $iv);
var_dump($decrypted);
$decompressed = decompress($compressed);
var_dump($decompressed);
Вывод этого скрипта следующий:
string(39) "img=/dir/dir/hi-res-img.jpg&w=700&h=500"string(30) "<��(��tJ��@�xH��G&(�%��%��xW"string(44) "xozYGselci9i70cTdmpvWkrYvGN9AmA7djc5eOcFoAM="string(30) "<��(��tJ��@�xH��G&(�%��%��xW"string(39) "img=/dir/dir/hi-res-img.jpg&w=700&h=500"
Вы увидите весь цикл: сжатие> шифрование> base64 кодирование / декодирование> дешифрование> декомпрессия. Вывод этого будет как можно ближе, насколько это действительно возможно, на максимально короткой длине, которую вы можете получить.
Ко всему прочему, я чувствую себя обязанным заключить это с тем фактом, что это чисто теоретический вопрос, и об этом было приятно подумать. Определенно есть лучшие способы достичь желаемого результата — я буду первым, кто признает, что мое решение немного абсурдно!
Вместо того, чтобы кодировать URL, как насчет вывода миниатюрной копии исходного изображения? Вот что я думаю:
1) Создайте «карту» для php, назвав ваши картинки (реальные имена файлов) случайными символами. Random_bytes отличное место для начала.
2) Вставьте желаемое разрешение в рандомизированную строку URL из # 1.
3) Используйте imagecopyresampled функция, чтобы скопировать исходное изображение в разрешение, которое вы хотите вывести, прежде чем выводить его на устройство клиента.
Так, например:
1 — Пример имени файла (из bin2hex(random_bytes(6))
): a1492fdbdcf2.jpg
2 — Требуемое разрешение: 800×600. Моя новая ссылка может выглядеть так:
http://myserver.com/?800a1492fdbdcf2600
или, может быть http://myserfer.com/?a1492800fdbdc600f2
или, может быть, даже http://myserver.com/?800a1492fdbdcf2=600
в зависимости от того, где я хочу вложить разрешение в ссылку
3 — PHP будет знать, что имя файла a1492fdbdcf2.jpg, возьмите его, используйте imagecopyresampled для копирования в нужное вам разрешение и выведите его.
РЕДАКТИРОВАТЬ
Читая выше и ниже комментарии, вам нужно решение, чтобы скрыть реальный путь вашего анализатора изображений, придав ему фиксированную ширину изображения.
http://www.example.com/tn/full/animals/images/lion.jpg
Вы можете достичь базового «thumbnailer», взяв прибыль .htaccess
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule tn/(full|small)/(.*) index.php?size=$1&img=$2 [QSA,L]
Ваш PHP-файл:
$basedir="/public/content/";
$filename=realpath($basedir.$_GET["img"]);
## check that file is in $basedir
if ((!strncmp($filename, $basedir, strlen($basedir))
||(!file_exists($filename)) die("Bad file path");
switch ($_GET["size"]) {
case "full":
$width=700;
$height=500;
## you can also use getimagesize() to test if the image is landscape or portrait
break;
default:
$width=350;
$height=250;
break;
}
## here is your old code for resizing images
## Note that the "tn" directory can exist and store the actual reduced images
Это позволяет вам использовать URL www.example.com/tn/full/animals/images/lion.jpg
чтобы просмотреть уменьшенное изображение.
Это имеет преимущество для SEO, чтобы сохранить оригинальное имя файла.
http://www.example.com/tn/full/lion.jpg
Если вам нужен более короткий URL, если у вас не слишком много изображений, вы можете использовать базовое имя файла (например, «lion.jpg») и рекурсивно искать. При столкновении используйте индекс, чтобы определить, какой вы хотите (например, «1 — lion.jpg»)
function matching_files($filename, $base) {
$directory_iterator = new RecursiveDirectoryIterator($base);
$iterator = new RecursiveIteratorIterator($directory_iterator);
$regex_iterator = new RegexIterator($iterator, "#$filename\$#");
$regex_iterator->setFlags(RegexIterator::USE_KEY);
return array_map(create_function('$a', 'return $a->getpathName();'), iterator_to_array($regex_iterator, false));
}
function encode_name($filename) {
$files=matching_files(basename($filename), realpath('public/content'));
$tot=count($files);
if (!$tot) return NULL;
if ($tot==1) return $filename;
return "/tn/full/".array_search(realpath($filename), $files)."--".basename($filename);
}
function decode_name($filename) {
$i=0;
if (preg_match("#^([0-9]+)--(.*)#", $filename, $out)) {
$i=$out[1];
$filename=$out[2];
}
$files=matching_files($filename, realpath('public/content'));
return $files ? $files[$i] : NULL;
}
echo $name=encode_name("gallery/animals/images/lion.jpg").PHP_EOL;
## --> returns lion.jpg
## You can use with the above solution the url http://www.example.com/tn/lion.jpg
echo decode_name(basename($name)).PHP_EOL;
## -> returns the full path opn disk to the image "lion.jpg"
Исходное сообщение:
По сути, если вы добавите некоторое форматирование в вашем примере, ваш сокращенный URL будет фактически длиннее:
img=/dir/dir/hi-res-img.jpg&w=700&h=500 // 39 chars
y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA // 50 chars
С помощью base64_encode
всегда приводит к более длинным строкам. А также gzcompress
потребует меньше, чтобы хранить один случай различных символов; это не хорошее решение для небольших строк.
Так ничего не делая (или просто str_rot13
), безусловно, является первым вариантом, который следует рассмотреть, если вы хотите сократить ранее полученный результат.
Вы также можете использовать простой метод замены персонажа на ваш выбор:
$raw_query_string = 'img=/dir/dir/hi-res-img.jpg&w=700&h=500';
$from="0123456789abcdefghijklmnopqrstuvwxyz&=/ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// the following line if the result of str_shuffle($from)
$to="0IQFwAKU1JT8BM5npNEdi/DvZmXuflPVYChyrL4R7xc&SoG3Hq6ks=e9jW2abtOzg";
echo strtr($raw_query_string, $from, $to)."\n";
// Result: EDpL4MEu4MEu4NE-u5f-EDp.dmprYLU00rNLA00 // 39 chars
Из вашего комментария вы действительно хотите, чтобы «никто не получил изображение высокого разрешения».
Лучший способ добиться этого — создать контрольную сумму с закрытым ключом.
Encode:
$secret="ujoo4Dae";
$raw_query_string = 'img=/dir/dir/hi-res-img.jpg&w=700&h=500';
$encoded_query_string = $raw_query_string."&k=".hash("crc32", $raw_query_string.$secret);
Результат: img=/dir/dir/hi-res-img.jpg&w=700&h=500&k=2ae31804
Decode:
if (preg_match("#(.*)&k=([^=]*)$#", $encoded_query_string, $out)
&& (hash("crc32", $out[1].$secret) == $out[2])) {
$decoded_query_string=$out[1];
}
Это не скрывает исходный путь, но этот путь не имеет причин быть публичным, ваш «index.php» может вывести ваше изображение из локального каталога после проверки ключа.
Если вы действительно хотите сократить исходный URL-адрес, вы должны ограничить допустимые символы в исходном URL-адресе. Многие методы сжатия основаны на том факте, что вы можете использовать полный байт для хранения не только символа.
Есть много способов сократить URL. Вы можете посмотреть, как другие сервисы, такие как TinyUrl, сокращают свои URL. Вот хорошая статья о хэшах и сокращающих URL: http://blog.codinghorror.com/url-shortening-hashes-in-practice/
Вы можете использовать функцию php mhash () для применения хешей к строкам: http://php.net/manual/en/function.mhash.php
И если вы прокрутите вниз до «Доступные хэши» на веб-сайте mhash, вы увидите, какие хэши вы можете использовать в функции (хотя я бы проверил, какие версии php имеют какие функции):http://mhash.sourceforge.net/mhash.3.html
Я думаю, что это было бы лучше сделать, не затеняя вообще. Вы можете просто кэшировать возвращенные изображения и использовать обработчик для их предоставления. Для этого необходимо, чтобы размеры изображения были жестко запрограммированы в php-скрипте. Когда вы получаете новые размеры, вы можете просто удалить все в кеше, так как он «лениво загружен».
1. Получить изображение из запроса
Это может быть так: /thumbnail.php?image=img.jpg&album=myalbum
, Можно даже сделать что-нибудь, используя переписать и иметь URL-адрес, например: /gallery/images/myalbum/img.jpg
,
2. Проверьте, не существует ли временная версия.
Вы можете сделать это используя is_file()
,
3. Создайте его, если он не существует
Используйте текущую логику изменения размера, чтобы сделать это, но не выводите изображение. Сохраните его во временном местоположении.
4. Считайте содержимое временного файла в поток
Практически просто выведите его.
Вот непроверенной пример кода …
<?php
// assuming we have a request /thumbnail.php?image=img.jpg&album=myalbum
// these are temporary filenames places. you need to do this yourself on your system.
$image = $_GET['image']; // the file name
$album = $_GET['album']; // the album
$temp_folder = sys_get_temp_dir(); // temp dir to store images
// (this should really be a specific cache path)
$image_gallery = "images"; // root path to the image gallery
$width = 700;
$height = 500;
$real_path = "$image_gallery/$album/$image";
$temp_path = "$temp_folder/$album/$image";
if(!is_file($temp_path))
{
// read in the image
$contents = file_get_contents($real_path);
// resize however you are doing it now.
$thumb_contents = resizeImage($contents, $width, $height);
// write to temp
file_put_contents($temp_path, $thumb_contents);
}
$type = 'image/jpeg';
header('Content-Type:'.$type);
header('Content-Length: ' . filesize($temp_path));
readfile($temp_path);
?>
Вы просто не сможете защитить свою ссылку, если где-то не хранится «секретный пароль»: пока URI несет всю информацию для доступа к вашему ресурсу, тогда он будет декодироваться и ваша «пользовательская безопасность» (они противоположны слова между прочим) легко сломаются.
Вы все еще можете добавить соль в свой код PHP (например, $mysalt="....long random string..."
) так как я сомневаюсь, что вы хотите вечную безопасность (такой подход слаб, потому что вы не можете возобновить $mysalt
ценность, но в вашем случае, несколько лет безопасности звучит достаточно, так как в любом случае, пользователь может купить одну фотографию и поделиться ею в другом месте, нарушая любой из ваших механизмов безопасности).
Если вы хотите иметь безопасный механизм, используйте хорошо известный (как это было бы с фреймворком) вместе с механизмом аутентификации и управления правами пользователя (чтобы вы могли знать, кто ищет ваш образ и разрешено ли ему это делать).
Безопасность имеет цену, если вы не хотите позволить себе ее вычисления & сохраняя требования, тогда забудьте об этом.
Если вы хотите избежать простого обхода пользователей и получить изображение в полном разрешении, тогда вы можете просто подписать URI (но на самом деле, для безопасности, используйте что-то, что уже существует, вместо этого краткого примера черновика ниже):
$salt = '....long random stirng...';
$params = array('img' => '...', 'h' => '...', 'w' => '...');
$p = http_build_query($params);
$check = password_hash($p, PASSWORD_BCRYPT, array('salt' => $salt, 'cost' => 1000);
$uri = http_build_query(array_merge($params, 'sig' => $check));
Декодирование:
$sig = $_GET['sig'];
$params = $_GET;
unset($params['sig']);
// Same as previous
$salt = '....long random stirng...';
$p = http_build_query($params);
$check = password_hash($p, PASSWORD_BCRYPT, array('salt' => $salt, 'cost' => 1000);
if ($sig !== $check) throw new DomainException('Invalid signature');
Увидеть http://php.net/manual/fr/function.password-hash.php
«Сокращение» с помощью общего алгоритма сжатия здесь бесполезно, потому что заголовки будут длиннее, чем URI, поэтому он почти никогда не будет сокращать его.
Если вы хотите сократить его, будьте умны: не указывайте относительный путь (/dir/dir
) если он всегда один и тот же (или только если он не основной). Не указывайте расширение, если оно всегда одно и то же (или дайте его, когда оно не png
если почти все в png
). Не дают height
потому что изображение несет aspect ratio
: вам нужно только width
, Дать его в x100px
если вам не нужна ширина с точностью до пикселя.
В своем вопросе вы утверждаете, что это должен быть чистый PHP, а не база данных, и должна быть возможность декодировать строки. Итак, немного изменив правила:
« `
class FooBarHashing {
private $hashes;
private $handle;
/**
* In producton this should be outside the web root
* to stop pesky users downloading it and geting hold of all the keys.
*/
private $file_name = './my-image-hashes.json';
public function __construct() {
$this->hashes = $this->get_hashes();
}
public function get_hashes() {
// Open or create a file.
if (! file_exists($this->file_name)) {
fopen($this->file_name, "w");
}
$this->handle = fopen($this->file_name, "r");$hashes = [];
if (filesize($this->file_name) > 0) {
$contents = fread($this->handle, filesize($this->file_name));
$hashes = get_object_vars(json_decode($contents));
}
return $hashes;
}
public function __destroy() {
// Close the file handle
fclose($this->handle);
}
private function update() {
$handle = fopen($this->file_name, 'w');
$res = fwrite($handle, json_encode($this->hashes));
if (false === $res) {
//throw new Exception('Could not write to file');
}
return true;
}
public function add_hash($image_file_name) {
$new_hash = md5($image_file_name, false);
if (! in_array($new_hash, array_keys($this->hashes) ) ) {
$this->hashes[$new_hash] = $image_file_name;
return $this->update();
}
//throw new Exception('File already exists');
}
public function resolve_hash($hash_string='') {
if (in_array($hash_string, array_keys($this->hashes))) {
return $this->hashes[$hash_string];
}
//throw new Exception('File not found');
}
}
« `
Пример использования:
<?php
// Include our class
require_once('FooBarHashing.php');
$hashing = new FooBarHashing;
// You will need to add the query string you want to resolve first.
$hashing->add_hash('img=/dir/dir/hi-res-img.jpg&w=700&h=500');
// Then when the user requests the hash the query string is returned.
echo $hashing->resolve_hash('65992be720ea3b4d93cf998460737ac6');
Таким образом, конечным результатом является строка, которая является только 32 chars long, что намного короче 52 у нас было раньше.
Боюсь, вы не сможете сократить строку запроса лучше, чем любой известный
алгоритм сжатия. Как уже упоминалось, сжатый
Версия будет короче на несколько (около 4-6) символов, чем оригинал.
Более того, исходная строка может быть относительно легко декодирована (например, в отличие от декодирования sha1 или md5).
Я предлагаю сократить URL-адреса с помощью конфигурации веб-сервера. Ты можешь
сократить его, заменив путь изображения с идентификатором (магазин ID-файл
пары в базе данных).
Например, следующее Nginx конфигурация принимает
URL-адреса как /t/123456/700/500/4fc286f1a6a9ac4862bdd39a94a80858
, где
123456
) должен быть идентификатором изображения из базы данных;700
а также 500
размеры изображения;# Adjust maximum image size
# image_filter_buffer 5M;
server {
listen 127.0.0.13:80;
server_name img-thumb.local;
access_log /var/www/img-thumb/logs/access.log;
error_log /var/www/img-thumb/logs/error.log info;
set $root "/var/www/img-thumb/public";
# /t/image_id/width/height/md5
location ~* "(*UTF8)^/t/(\d+)/(\d+)/(\d+)/([a-zA-Z0-9]{32})$" {
include fastcgi_params;
fastcgi_pass unix:/tmp/php-fpm-img-thumb.sock;
fastcgi_param QUERY_STRING image_id=$1&w=$2&h=$3&hash=$4;
fastcgi_param SCRIPT_FILENAME /var/www/img-thumb/public/t/resize.php;
image_filter resize $2 $3;
error_page 415 = /empty;
break;
}
location = /empty {
empty_gif;
}
location / { return 404; }
}
Сервер принимает только URL указанного шаблона, перенаправляет запрос на /public/t/resize.php
скрипт с измененной строкой запроса, затем изменяет размер изображения, сгенерированного PHP с image_filter
модуль. В случае ошибки возвращает пустое изображение GIF.
image_filter
не является обязательным, он включен только в качестве примера. Изменение размера может быть выполнено полностью на стороне PHP. Кстати, с помощью Nginx можно избавиться от части PHP.
Сценарий PHP должен проверять хеш следующим образом:
// Store this in some configuration file.
$salt = '^sYsdfc_sd&9wa.';
$w = $_GET['w'];
$h = $_GET['h'];
$true_hash = md5($w . $h . $salt . $image_id);
if ($true_hash != $_GET['hash']) {
die('invalid hash');
}
$filename = fetch_image_from_database((int)$_GET['image_id']);
$img = imagecreatefrompng($filename);
header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);