кодирование — максимально короткая закодированная строка с возможностью декодирования (сокращенный URL) с использованием только переполнения стека

Я ищу метод, который кодирует строку в самый короткий возможная длина, и пусть это будет декодируемыми (чистый PHP, без SQL). У меня есть рабочий скрипт, но я недоволен длиной закодированной строки.

СЦЕНАРИЙ:

Ссылка на изображение (зависит от разрешения файла, которое я хочу показать пользователю):

  • www.mysite.com/share/index.php?img=/dir/dir/hi-res-img.jpg&W = 700&ч = 500

Закодированная ссылка (поэтому пользователь не может угадать, как получить увеличенное изображение):

  • www.mysite.com/share/encodedQUERYstring

Итак, я бы хотел закодировать только часть поискового запроса в URL:

  • IMG = / DIR / DIR / привет разрешения-IMG.jpg&W = 700&ч = 500

Метод, который я использую сейчас, закодирует приведенную выше строку запроса в:

  • y8xNt9VPySwC44xM3aLUYt3M3HS9rIJ0tXJbcwMDtQxbUwMDAA

Метод, который я использую:

 $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?

13

Решение

Я подозреваю, что вам нужно больше думать о вашем методе хеширования, если вы не хотите, чтобы пользователь расшифровывал его. Вопрос с 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 кодирование / декодирование> дешифрование> декомпрессия. Вывод этого будет как можно ближе, насколько это действительно возможно, на максимально короткой длине, которую вы можете получить.

Ко всему прочему, я чувствую себя обязанным заключить это с тем фактом, что это чисто теоретический вопрос, и об этом было приятно подумать. Определенно есть лучшие способы достичь желаемого результата — я буду первым, кто признает, что мое решение немного абсурдно!

12

Другие решения

Вместо того, чтобы кодировать 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 для копирования в нужное вам разрешение и выведите его.

4

РЕДАКТИРОВАТЬ

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

Шаг 1 : 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, чтобы сохранить оригинальное имя файла.

Шаг 2 : 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.jp‌​g").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-адресе. Многие методы сжатия основаны на том факте, что вы можете использовать полный байт для хранения не только символа.

3

Есть много способов сократить 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

2

Я думаю, что это было бы лучше сделать, не затеняя вообще. Вы можете просто кэшировать возвращенные изображения и использовать обработчик для их предоставления. Для этого необходимо, чтобы размеры изображения были жестко запрограммированы в 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);
?>
2

Короткие слова о «безопасности»

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

Вы все еще можете добавить соль в свой код PHP (например, $mysalt="....long random string...") так как я сомневаюсь, что вы хотите вечную безопасность (такой подход слаб, потому что вы не можете возобновить $mysalt ценность, но в вашем случае, несколько лет безопасности звучит достаточно, так как в любом случае, пользователь может купить одну фотографию и поделиться ею в другом месте, нарушая любой из ваших механизмов безопасности).

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

Безопасность имеет цену, если вы не хотите позволить себе ее вычисления & сохраняя требования, тогда забудьте об этом.


Безопасный, подписав URL

Если вы хотите избежать простого обхода пользователей и получить изображение в полном разрешении, тогда вы можете просто подписать 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 если вам не нужна ширина с точностью до пикселя.

2

В своем вопросе вы утверждаете, что это должен быть чистый PHP, а не база данных, и должна быть возможность декодировать строки. Итак, немного изменив правила:

  • То, как я интерпретирую этот вопрос, заключается в том, что мы не слишком заботимся о безопасности, но нам нужны самые короткие хэши, которые приводят к изображениям.
  • Мы также можем воспользоваться «возможностью декодирования» с помощью щепотки соли, используя односторонний алгоритм хеширования.
  • Мы можем хранить хеши внутри объекта JSON, а затем сохранять данные в файле, поэтому все, что нам нужно сделать в конце дня, — это сопоставление строк.

« `

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 у нас было раньше.

1

Боюсь, вы не сможете сократить строку запроса лучше, чем любой известный
алгоритм сжатия. Как уже упоминалось, сжатый
Версия будет короче на несколько (около 4-6) символов, чем оригинал.
Более того, исходная строка может быть относительно легко декодирована (например, в отличие от декодирования sha1 или md5).

Я предлагаю сократить URL-адреса с помощью конфигурации веб-сервера. Ты можешь
сократить его, заменив путь изображения с идентификатором (магазин ID-файл
пары в базе данных).

Например, следующее Nginx конфигурация принимает
URL-адреса как /t/123456/700/500/4fc286f1a6a9ac4862bdd39a94a80858, где

  • первый номер (123456) должен быть идентификатором изображения из базы данных;
  • 700 а также 500 размеры изображения;
  • последняя часть MD5 хеш защиты от запросов с разными
    Размеры
    .
# 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);
0
По вопросам рекламы [email protected]