- Регистрация
- 25.09.2014
- Сообщения
- 194
- Благодарностей
- 118
- Баллы
- 43
Мне потребовалось сохранять значения куки контейнера в СУБД mysql, и вот к чему я пришел.Куки – это небольшие строки данных, которые хранятся непосредственно в браузере. Они являются частью HTTP-протокола, определённого в спецификации RFC 6265.
По ходу текста встречаем *, сноски смотрим ниже текста основного.
И так, в хранении кукисов непосредственно в ячейке со строковым параметров если рассматривать реляционную СУБД mysql/mariadb у нас есть две основные проблемы.
- Спец. символы в cookie. Все возможные звездочки палочки, слеши, кавычки.
- Длинна данных строки. Приближаясь к реалии, любой слепок 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 без компрессии оказалось вполне себе ощутимая разница.
Кто хочет повторить, 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);
}
}
}
Код берет сразу из куки контейнера данные текущего инстанса, выполняет компрессию и кодирует в 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 строка с куки-контейнером и всеми куками этого сеанса
Распаковываем:
// Присваиваем строку запакованные кук (предполагается что их забрали из БД)
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 дяде вызовет лишь смех, когда школа будет со слезами создавать десятый пост с вопросом "подскажите что за ШИФР" или "как РАСШИФРОВАТЬ", так и не поняв, что хеширование вообще не возможно расшифровать, а компрессия далеко от темы шифрования, собственно как и хеширования, но последнее куда ближе.
2) мы храним в удобном формате, делаем операцию безопаснее и менее геморройную исключая проблемы с длинной / спец символами.
p.s. только не говорите что я вас убеждал мол это как то безопасно) нет это не безопаснее с точки зрения формата, разве что с точки зрения оперирования с данными. Для безопасного хранения существуют практики хеширования и так далее. Это уже не входит в рамки данной задачи =)
Бонус. Утилита распаковки
Утилиту можете скомпилировать самостоятельно, исходники в cookie_decoder_src.zip, кому лень или сложно, забирайте скомпилированную версию под win x64 cookie_decoder_bin.zip (включает в и рантайм и все необходимое, поэтому такой вес)
Пример проекта с подробным описанием прикреплён в CompressCookies.zip
p.s.s. на эту статью потратилось в 4 раза больше времени чем на все шаги от проблемы до решения выше
Вложения
-
14,5 КБ Просмотры: 111
-
2 КБ Просмотры: 105
-
25,4 МБ Просмотры: 116
Для запуска проектов требуется программа ZennoPoster.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...
Для того чтобы запустить шаблон, откройте программу ZennoPoster. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.