Практика хранение Cookies в БД.

Hartwell

Client
Регистрация
25.09.2014
Сообщения
194
Реакции
119
Баллы
43
Куки – это небольшие строки данных, которые хранятся непосредственно в браузере. Они являются частью HTTP-протокола, определённого в спецификации RFC 6265.

Мне потребовалось сохранять значения куки контейнера в СУБД mysql, и вот к чему я пришел.
По ходу текста встречаем *, сноски смотрим ниже текста основного.

И так, в хранении кукисов непосредственно в ячейке со строковым параметров если рассматривать реляционную СУБД mysql/mariadb у нас есть две основные проблемы.
  1. Спец. символы в cookie. Все возможные звездочки палочки, слеши, кавычки.
  2. Длинна данных строки. Приближаясь к реалии, любой слепок cookies netscape или иных двух форматах могут достигать приличных размеров.*
----- Рассмотрение специфики проблем
1. По спец. символам: мы можешь обработать строку перед добавлением в БД, выполнив операции escape спец символов, но честно говоря мне даже лениво задумываться над всеми "ЕСЛИ" которые содержит данный способ, сразу отбросил его.
А если спец символы?
А если выполнится подзапрос sql который DROP'нит все бд (мы ведь понятия не имеем что лежит в куках?
А если куки выйдут за рамки размера хранения ячейки в бд? (обрежем половину кук?) и тд.) **
2. По конечно хотелось бы все это уместить в varchar, но понятное дело что это на грани фантастики. Но точно никакие blob, hex, и другие специфичные типы данных в БД. Оптимальным будет след. после varchar - text ***

---- Реализация.

Не претендую на роль лучшего решения, в нем есть свои плюсы и минусы, но решение основных проблем покрываем полностью.
Снова к вопросу номер "1". Для адекватного и безопасного хранения разумным шагом будет привести к более пригодному формату для хранения там, где у нас имеется типизация и другие относительно строгие правила к передачи обработке данных. Первым сразу же приходит на ум - Base64, даже учитывая что он не идеальный**** но в данном случае он полностью приемлем, да и не особо хочется таскать зависимости в виде сторонних библиотек.

На первый взгляд казалось бы все, base64 encode/decode - таска выполнена. Перевод в base64 существенно увеличивает длинну данных (мы же используем меньший набор символов, всего из 64). В base58 - логично оно будет еще длиннее и так далее (так как набор включает 58 символов используемых)
Собственно пруф. 40 символов в base64 будет уже 58 занимать.
C#:
Развернуть Свернуть Копировать
// Raw string:
Set-Coookie: Key=Value; domain=.goolge;/
// Base64encode of string below
U2V0LUNvb29raWU6IEtleT1WYWx1ZTsgZG9tYWluPS5nb29sZ2U7Lw==

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

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

Конечно же нам нужна без потерь, куки нам важны и хотим целыми сохранить, и целыми их получить в итоге.

Провел эксперимент, взял набор кукисов привел в base64 сравнил размер с исходными. Еще раз взял куки, сжал исходные куки - получил набор байтов, набор байтов сконвертировал в base64 сравнил их с base64 без компрессии оказалось вполне себе ощутимая разница.
Кто хочет повторить, CyberChef там все необходимое. Compress ищем и base64. Все тривиально.

------ Сноски:
* - есть утверждение об максимальном размере файла с куками браузера - 4кб (до ~300 пар Ключ=Значение), но в современных версиях оно может быть расширено до 8кб (~500 Пар ключ значение).
** - Костыль java'истов, encoding cookie fix- как факт, куки требуют доп. обработки. К тому-же у нас еще и СУБД
*** - Формат TEXT: представляет текст длиной до 65 КБ в mysql.
**** - В википеди на сколько помню можно прочитать про это все. Суть в том что base64 все же содержит символы которые в некоторых случаях при работе с вебом имеет проблемные символы. (имеется ввиду общий подход независимо от ПО/решения/реализации). Был придуман формат base64 для url решающий проблему при работе с веб адресами. В последствии вышел base58 который исключает проблемные символы, почти идеальный формат не содержащий управляющие символы и тд.
***** - Для тех кто не въехал, причем же тут какие-то base58/64, наборы символов и тд. Выполняем задачку - получаем полное понимание. Задача: Взять строку "привет мир" и привести в бинарный формат (где используется вообще всего набор из 2х символов - ноль и единица), и посчитать длину данных исходных и в бинарном формате. Base64 подобен бинарному, но используется набор из 64символов, base58 - из 58, base32 - очевидно 32 и так далее. Чем меньше набор тем длиннее запись, тем больше места потребуется.


Реализация кода
Ну и сам код. Потребуется библиотека компрессии. Она есть, в поставке с фреймворком, находится в окружении System.IO.Compressiob
Находим ее в GAS (прямо в списке, dll не требуется так как они уже должны присутствовать, поэтому просто добавляем ссылку в gas выбрав из списка ее)

Класс с методами сжатия + кодирования base64 и обратная операция:
Method for compress/decompress:
Развернуть Свернуть Копировать
using System.IO.Compression; // Добавляем системную либу компрессии

namespace Compress // Создаем новый namespace ля операции с куками
{
    /*
    var uncompressedString = "Hello World!";
    var compressedString = Compress.StringCompression.Compress(uncompressedString);

    var decompressedString = Compress.StringCompression.Decompress(compressedString);
    return decompressedString;   
    */
    public static class StringCompression
    {
        /// <summary>
        /// Compresses a string and returns a deflate compressed, Base64 encoded string.
        /// </summary>
        /// <param name="uncompressedString">String to compress</param>
        public static string Compress(string uncompressedString)
        {
            byte[] compressedBytes;
            using (var uncompressedStream = new MemoryStream(Encoding.UTF8.GetBytes(uncompressedString)))
            {
                using (var compressedStream = new MemoryStream())
                {
                    // setting the leaveOpen parameter to true to ensure that compressedStream will not be closed when compressorStream is disposed
                    // this allows compressorStream to close and flush its buffers to compressedStream and guarantees that compressedStream.ToArray() can be called afterward
                    // although MSDN documentation states that ToArray() can be called on a closed MemoryStream, I don't want to rely on that very odd behavior should it ever change
                    using (var compressorStream = new DeflateStream(compressedStream, CompressionLevel.Fastest, true))
                    { uncompressedStream.CopyTo(compressorStream); }
                    // call compressedStream.ToArray() after the enclosing DeflateStream has closed and flushed its buffer to compressedStream
                    compressedBytes = compressedStream.ToArray();
                }
            }
            return Convert.ToBase64String(compressedBytes);
        }

        /// <summary>
        /// Decompresses a deflate compressed, Base64 encoded string and returns an uncompressed string.
        /// </summary>
        /// <param name="compressedString">String to decompress.</param>
        public static string Decompress(string compressedString)
        {
            byte[] decompressedBytes;
            var compressedStream = new MemoryStream(Convert.FromBase64String(compressedString));
            using (var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
            {
                using (var decompressedStream = new MemoryStream())
                {
                    decompressorStream.CopyTo(decompressedStream);
                    decompressedBytes = decompressedStream.ToArray();
                }
            }
            return Encoding.UTF8.GetString(decompressedBytes);
        }
    }
}

Остается лишь заюзать метод в кубике c# код
Код берет сразу из куки контейнера данные текущего инстанса, выполняет компрессию и кодирует в base64. Получаем строку которую можно сразу отправлять в бд.
Сжимаем:
Развернуть Свернуть Копировать
var packCookie = Encoding.UTF8.GetString(project.Profile.CookieContainer.Export()); // достаем куки из куки контейнера
var pbCoookie = Compress.StringCompression.Compress(packCookie); // пакуем компрессией в pbCookie + encode Base64
string strComprCookies = pbCoookie.ToString(); // Приводим к строке из добавленного метода в OwnCode, получаем base64 строку

project.Variables["compessedCookie"].Value = strComprCookies; // Сохраним в переменной чтобы в след. кубике распаковать для демонстрации (и не более)

return strComprCookies;
// в ретурн у нас находится готовая к INSERT строка с куки-контейнером и всеми куками этого сеанса

Распаковка выполняется одной строкой. Получаем кукисы в исходном формате из сжатой + base64 строки с нашим набором кукисов.
Распаковываем:
Развернуть Свернуть Копировать
// Присваиваем строку запакованные кук (предполагается что их забрали из БД)
var compressedString  = project.Variables["compessedCookie"].Value;

var decompressedString = Compress.StringCompression.Decompress(compressedString); // Распаковываем + decode base64 = получаем raw string Cookie (какие были изначально)

project.SendInfoToLog("Raw Cookies: "+ decompressedString, true);

project.Variables["rawCookies"].Value = decompressedString;

return decompressedString;

Ну а теперь о плюсах и минусах.
Минусы - мы не можем взять из БД куки путем копи паст. Нам потребуется что-то что еще и распакует. Это может быть не всегда практично. Но в моей задачи не предполагалось взаимодействия юзера с этими данными вручную. Но все же сделал сразу же мини утилиту консольную которая расколдовывает сжатый набор кукисов.

Плюсы - 1) там где минус там и плюс, такой подход вполне может добавить чуточку безопасности хранения, от всякого рода школы /скрипт кидди. Естественно более менее познавшего мир и суровость cyber security дяде вызовет лишь смех, когда школа будет со слезами создавать десятый пост с вопросом "подскажите что за ШИФР" или "как РАСШИФРОВАТЬ", так и не поняв, что хеширование вообще не возможно расшифровать, а компрессия далеко от темы шифрования, собственно как и хеширования, но последнее куда ближе.:ap:
2) мы храним в удобном формате, делаем операцию безопаснее и менее геморройную исключая проблемы с длинной / спец символами.



p.s. только не говорите что я вас убеждал мол это как то безопасно) нет это не безопаснее с точки зрения формата, разве что с точки зрения оперирования с данными. Для безопасного хранения существуют практики хеширования и так далее. Это уже не входит в рамки данной задачи =)


Бонус. Утилита распаковки
Утилиту можете скомпилировать самостоятельно, исходники в cookie_decoder_src.zip, кому лень или сложно, забирайте скомпилированную версию под win x64 cookie_decoder_bin.zip (включает в и рантайм и все необходимое, поэтому такой вес)

79754

Пример проекта с подробным описанием прикреплён в CompressCookies.zip

p.s.s. на эту статью потратилось в 4 раза больше времени чем на все шаги от проблемы до решения выше *HAHA*
 

Вложения

легких путей не ищем
varbinary на 64К и пофиг что там в куках и на их размер, экранировать не забыть при сохранении в БД
 
Последнее редактирование:
легких путей не ищем
varchar binary на 64К и пофиг что там в куках и на их размер, экранировать не забыть при сохранении в БД
Не читаем статью =) В условиях же сказал что не хочу бинарные форматы, и экранирование не панацея. Гляньте rfc, куки могут не только буковки цифорки, а any symbols из набора us какойто там..


И говорю что не претендую на лучшее решение. Однако вполне как уневерсальное. Что же если понадобится вдруг не куки а что-то размером в 60кб ?) Данное решение уместит такое в тот же text поле, а у вас еще накинутся экранирования + бинарный формат (см сноски) тоже прибавят размер данных.


В целом вообще смысл статьи в подходе, но написав что-то вроде Компрессия данных блаблабла явно менее было бы привлекательно чем пример с куками. К томуже еще и придумывать кейсы какие-то и где использовать. Но я топлю за подход, который приемлим к многим задачкам. Ну и в целом тут очень многое расписано по целой сериии моментов которые любому новичку попросту не будут очевидными. Это и форматы хранения, зачем они вообще, в чем прикол base всяких и тд и тп. =)

Но по всему что вы сказали, абсолютно, все оговрено это еще в самой статье =) Можно так то вообще в тхт, можно взять nosql и ниче не паковать ни конвертить, вариантов больше сотни это и так понятно. Можно вообще на стороне бд выполнять меджик процедурками. Все это можно =) Но как нужно, все-же определяет задача)
 
А для чего это нужно в принципе?
 
За то время пока Вы разбирались и писали код и пост, Вы могли бы с большим эффектом прочитать документацию на MySQL.
Тогда вы не выдали бы такое

Гляньте rfc, куки могут не только буковки цифорки, а any symbols из набора us какойто там..

Форматы Binary для того и созданы в MySql что бы хранить пофигу какие символы.
Фраза
а у вас еще накинутся экранирования + бинарный формат
значит только абсолютное непонимание работы БД.
символы экранирования не хранятся в бд если что.

А что касается компрессии...
жмите хоть гзипом хоть чертом лысым, формату varbinary пофиг что хранить.

не хочу бинарные форматы
Использование бинарных форматов и есть универсальный подход для любых задач, а остальное пустая трата времени
меджик процедурками
инфоцыган не стоит цитировать, нет такого понятия в mysql
:-)
 
  • Спасибо
Реакции: one
За то время пока Вы разбирались и писали код и пост, Вы могли бы с большим эффектом прочитать документацию на MySQL.
Тогда вы не выдали бы такое



Форматы Binary для того и созданы в MySql что бы хранить пофигу какие символы.
Фраза

значит только абсолютное непонимание работы БД.
символы экранирования не хранятся в бд если что.

А что касается компрессии...
жмите хоть гзипом хоть чертом лысым, формату varbinary пофиг что хранить.


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

инфоцыган не стоит цитировать, нет такого понятия в mysql
:-)
Я не специалист по MySQL, но сказано сильно!:ce:
 
За то время пока Вы разбирались и писали код и пост, Вы могли бы с большим эффектом прочитать документацию на MySQL.
Тогда вы не выдали бы такое
За 30мин + 60мин осилите доку ?) Респект если так.

Форматы Binary для того и созданы в MySql что бы хранить пофигу какие символы.


Формат бинари создан для того чтобы хранить бинарные форматы, т.е. всякого рода файлы картинки и тд. В тоже время TEXT для хранения строковых данных.

что бы хранить пофигу какие символы.
Если быть точнее 255 вариантов против 77 или около того относящихся к текстовым с полем TEXT. В примере 64 всего используется, для чего тут еще 191 ? конвертируем же base.


BLOBзначения обрабатываются как двоичные строки (байтовые строки). У них есть binary набор символов и сопоставление, а сравнение и сортировка основаны на числовых значениях байтов в значениях столбцов. TEXTзначения обрабатываются как недвоичные строки (символьные строки). У них есть набор символов, отличный от binary, и значения сортируются и сравниваются на основе сопоставления набора символов.
MySQL Connector / ODBC определяет BLOBзначения как LONGVARBINARYи TEXT значения как LONGVARCHAR.

значит только абсолютное непонимание работы БД.
символы экранирования не хранятся в бд если что.
Скорее не об-мыслил, логично что это указания для парсера и не более. Что-же этот факт определяет уровень познания БД? забавно

А что касается компрессии...
жмите хоть гзипом хоть чертом лысым, формату varbinary пофиг что хранить.
К чему это? Бинари не жмет сам по себе
LENGTH(`binary`.`binary`)
3719

LENGTH(compres.compressed)
1512

А то что ее можно сжать и потом положить в бинари - спасибо кэп, но зачем? Нагрузить лишними операциями, если у нас строковой base64

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

Не спорю, однако, с TEXT мы не привязаны к формату, если захотим перенести из mysql куда-то где используются только строковые. В бинари если только hex но опять же разницу выше приводил и она в 2 раза, это еще без учета перевода данных в hex, хотя конечно же в данном случае он на вовсе и не нужен.

И того резюмируем:
Вы топите за бинари, он лишь отличается от text тем что хранится в бинарном формате, мы можем туда класть че угодно - ок, но копипастом мы не заберем из таблички к примеру, да и вообще любое перемещение потребует лишних операций типо перевода в исходный формат либо hex если присутствуют not printble символы.
С TEXT у нас все в строках, не нужно плясок. (вы наверно у себя текстовые файлы сохраняете в .jpg или .bin, еще и приводите в бинарный формат? или все же если текст - то храните как текст а не картинки и тд)

Компрессию прокоментировали непонятно к чему, делаю вывод что бинари не решит проблему длинных данных без дополнительной компрессии, верно? Все вопросы с спец символами решает код. Приведите хоть один разумный аргумент зачем мне хранить строку base64 в binary нежели я это сделаю в TEXT. Аргументируйте?

p.s.s ну и пользуясь случаем, хочу спросить у гуру субд. куки - это юзерский инпут верно? допустим у нас реализация хранения кук в веб проекте, любой юзер может подсунуть любые значения в куки. В моем варианте нам плевать на все, что там будет, так как в бд попадает base64. А что-же в случае если бы мы сохраняли в бинарном, безопасно ли принимать без пред-обработки эти данные и сохранять в бинарном формате? Имею ввиду sql inj, и прочее. Честно я без понятия, поведайте поведение обработки юзерских инпутов в случае если просто сохранить бинарем?
 
Последнее редактирование:
В бинари если только hex но опять же разницу выше приводил и она в 2 раза
Жуткий секрет открою
Если взять строку
любую
с любыми символами
и тупо положить обычным инсертом в varbinary - она туда спокойно ляжет, и она же оттуда будет получена селектом. Каким концом здесь HEX?

наворачивать base64 на данные который нужно положить в БД?
Зачем?
Просто положить не судьба?
base64 для других целей разработан, в первую очередь для передачи данных протоколами поддерживающими только текстовые данные типа SMTP.

куки - это юзерский инпут верно?
Нет
Вы бы сначала почитали что такое куки, это уже за гранью
 
Каким концом здесь HEX?
эмм... вы вообще читаете что вам отвечают?
[
QUOTE="Hartwell, post: 628264, member: 8099"]
хотя конечно же в данном случае он на вовсе и не нужен.
[/QUOTE]


наворачивать base64 на данные который нужно положить в БД?
Зачем?
Просто положить не судьба?
Не судьба, и об этом много сказано в первом посте.


base64 для других целей разработан, в первую очередь для передачи данных протоколами поддерживающими только текстовые данные типа SMTP.
Да ну... https://ru.wikipedia.org/wiki/Base64#Применение_в_веб-приложениях


Нет
Вы бы сначала почитали что такое куки, это уже за гранью
Покажите пруф данного предположения?


И я так и не увидел аргумента...
Приведите хоть один разумный аргумент зачем мне хранить строку base64 в binary нежели я это сделаю в TEXT.
 
Аргументов в этом случае мало.
 

Кто просматривает тему: (Всего: 0, Пользователи: 0, Гости: 0)