Если в этом посте вам в основном интересно, как что ломается, сразу можете переходить к последнему разделу.
HTTP-куки — это небольшие информационные добавки, направляемые на клиент с сервера, работающего с JavaScript или HTTP. Куки играют определяющую роль для поддержки состояния во всем вам известной Всемирной Паутине — системе, где иного способа сохранять состояние не предусмотрено. Как только куки установлены, браузеры станут переадресовывать их в нагрузку ко всем HTTP-запросам, у которых правильно выставлена область видимости — до тех пор, пока срок действия куки не истечёт.
Меня более чем устраивало просто не вдаваться в глубокие детали и просто абстрагироваться от того, как именно функционируют куки, пока однажды я не наткнулась на следующий код JavaScript — казалось бы, совершенно безобидный:
const favoriteCookies = JSON.stringify({ ginger: "snap", peanutButter: "chocolate chip", snicker: "doodle", }); document.cookie = ` cookieNames=${favoriteCookies}` ;
На уровне браузеров этот код работал совершенно нормально. Он зачерпывал скучного (но вкусного) JSON и сохранял значение в сеансовом куки. Притом, что это выглядело немного странно — ведь обычно код сериализует JSON до base64, а только затем отправляет его в виде куки. Здесь не просматривалось никаких проблем, которые могли бы озадачить браузер. Все браузеры с готовностью разрешали установить куки, после чего посылали их прямо в HTTP-заголовке на веб-сервер базы данных:
GET / HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Connection: keep-alive Cookie: cookieNames={"ginger":"snap","peanutButter":"chocolate chip","snicker":"doodle"} Host: example.com
Всё шло хорошо, пока не пришлось передавать некоторый код, в котором использовалась стандартная библиотека Go. Стандартная библиотека Go оказалась не в состоянии разобрать куки, что привело к лавинообразным отказам вплоть до самой вершины стека. Так что же случилось?
Спецификация
Куки-файлы были впервые описаны в 1997 году в документе RFC 2109, а впоследствии актуализированы в документах RFC 2965 (2000) и RFC 6265 (2011). Также есть ещё один черновой документ, который прямо сейчас в процессе обновления (и именно он используется в этой статье).
В RFC есть два раздела, касающихся значений куки:
Раздел 4.1.1 (о том, как серверы должны отправлять куки)
Неофициально куки содержится в поле Set-Cookie ответного заголовка, который начинается с пары имя-значение, а за этой парой следуют ноль или более пар атрибут-значение. Серверам НЕ СЛЕДУЕТ отправлять такие заголовочные поля Set-Cookie, которые не соответствуют следующей грамматике: set-cookie = set-cookie-string set-cookie-string = BWS cookie-pair *( BWS ";" OWS cookie-av ) cookie-pair = cookie-name BWS "=" BWS cookie-value cookie-name = 1*cookie-octet cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E ; Символы US-ASCII за исключением управляющих, ; пробельного DQUOTE, запятой, точки с запятой ; и обратного слэша
Раздел 5.6 (о том, как браузеры должны принимать куки)
Для синтаксического разбора строки set-cookie пользовательский агент ОБЯЗАН использовать алгоритм, эквивалентный следующему: 1. Если в строке set-cookie-string содержится символ %x00-08 / %x0A-1F / %x7F, (управляющие символы за исключением символа горизонтальной табуляции): То эти шаги следует отбросить и полностью игнорировать строку set-cookie-string. 2. Если в строке set-cookie-string содержится символ %x3B (";"): то строка с парой имя-значение состоит из всех символов вплоть до первого %x3B (";"), но не включая его, а строка unparsed-attributes представляет собой оставшуюся часть рассматриваемой строки set-cookie-string (включая символ %x3B (";"). Иначе: 1. Строка с парой «имя-значение» состоит из символов, содержащихся в строке set-cookie-string, а unparsed-attributes представляет собой пустую строку.
Здесь есть три аспекта, которые должны сразу же броситься вам в глаза:
-
То, что СЛЕДУЕТ отправлять серверам, не соответствует тому, что ОБЯЗАНЫ принимать браузеры. Классический пример трагедии, которая возникает, если придерживаться закона Постела.
-
Здесь не указано никаких условий, которые диктовали бы, какие куки неприемлемо отправлять их браузера на сервер — не считая оговорки о точке с запятой в качестве разделительного знака. Это было бы нормально, если бы серверы принимали только те куки, которые отправили сами. Но ведь куки могут поступать и из document.cookie и содержать иные значения кроме %x21, %x23-2B, %x2D-3A, %x3C-5B и %x5D-7E, разрешённых в Set-Cookie.
-
В стандарте не указано, как должны действовать стандартные библиотеки, обрабатывающие заголовки Cookie: как пользовательские агенты или как серверы? Должны ли они действовать по принципу послабления или предписания? Должны ли их действия варьироваться в зависимости от контекста?
В этом и заключается вся суть той проблемы, с которой мне в итоге довелось столкнуться: везде всё устроено по-разному, и просто чудо, что куки вообще работают.
Браузеры
Для начала давайте разберёмся, как работают браузеры. Команды, поддерживающие Gecko (Firefox), Chromium и WebKit (Safari) постоянно взаимодействуют друг с другом, поэтому было бы логично ожидать, что все эти инструменты будут работать одинаково… верно?
Прежде, чем углубиться в детали, напомню: RFC сам себе противоречит, поскольку там сказано, что в заголовках Set-Cookie могут содержаться любые символы кодировки ASCII кроме управляющих, двойных кавычек, запятых, точек с запятой и обратных слэшей, но при этом утверждается, что браузерам следует принимать куки с любыми значениями, если только они не содержат управляющих символов.
Firefox
Код для валидных значений куки в Firefox выглядит примерно так:
bool CookieCommons::CheckValue(const CookieStruct& aCookieData) { // отклонить куки, если в значении содержатся любые символы, не разрешённые в RFC 6265 - см. // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423 // ЗАМЕЧАНИЕ: это не полный набор символов, не разрешённых в 6265 – обратите внимание, что // в этом списке отсутствуют 0x09, 0x20, 0x22, 0x2C и 0x5C. const char illegalCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x3B, 0x7F, 0x00}; const auto* start = aCookieData.value().BeginReading(); const auto* end = aCookieData.value().EndReading(); auto charFilter = [&](unsigned char c) { if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) { return true; } return std::find(std::begin(illegalCharacters), std::end(illegalCharacters), c) != std::end(illegalCharacters); }; return std::find_if(start, end, charFilter) == end; }
† раньше символ 0x7F принимался, и этот баг был исправлен под номером 1797235 (Firefox 108)
Firefox принимает пять таких символов, которые сервер в соответствии с рекомендацией RFC сервер не должен отправлять:
-
0x09 (горизонтальная табуляция)
-
0x20 (пробелы)
-
0x22 (двойные кавычки)
-
0x2C (запятые)
-
0x5C (обратные слэши)
Исходно это было сделано давным-давно для обеспечения паритета с Chrome, и следы этого решения до сих пор сохранились в обеих базах кода.
Самые внимательные из вас могли заметить, что в Firefox есть настройка network.cookie.blockUnicode, с которой сверяется этот код, и которая отвергает любые значения выше 0x80. Данная базовая работа была проделана в результате долгого исследования, и за её ходом можно проследить в отчёте по багу 1797231.
Chromium
В Chromium код для валидных куки выглядит примерно так:
bool ParsedCookie::IsValidCookieValue(const std::string& value) { // Функция IsValidCookieValue() возвращает, соответствует ли строка следующей // грамматике: // // cookie-value = *cookie-value-octet // cookie-value-octet = %x20-3A / %x3C-7E / %x80-FF // ; octets excluding CTLs and ";" // // Таким образом можно определять, содержатся ли в значениях куки // какие-либо недопустимые символы. // // Обратите внимание: в разделе 4.1.1 документа RFC6265bis section 4.1.1 предлагается более сложная // грамматика для разбора значений куки, но мы предпочитаем разрешить более широкий диапазон символов, // чем допускается в рамках этой грамматики (не противореча при этом требованиям, предъявляемым к // алгоритму разбора, которые определены в разделе 5.2). // // Для справки см.: // - https://crbug.com/238041 for (char i : value) { if (HttpUtil::IsControlChar(i) || i == ';') return false; } return true; } // В приложении B.1 указано, // является ли конкретный символ управляющим (CTL) в соответствии с документом RFC 5234 static inline bool IsControlChar(char c) { return (c >= 0x00 && c <= 0x1F) || c == 0x7F; }
В Chrome ограничения чуть строже, чем в Firefox — он отказывается принимать в значениях куки символ 0x09 (горизонтальная табуляция).
Тем не менее (что, кстати, противоречит RFC), он с готовностью принимает и отправляет пробелы, двойные кавычки, запятые, обратные слэши и символы Юникода.
Safari (WebKit)
Мне не удалось получить доступ к коду хранилища куки, поскольку этот код находится в закрытом исходнике CFNetwork. Тем не менее, мне удалось заглянуть внутрь браузерного движка, выполнив следующий код на JavaScript:
for (i=0; i<256; i++) { let paddedIndex = i.toString().padStart(3, '0') + '_' + '0x' + i.toString(16).padStart(2, '0'); // установить куки с именем "cookie" + десятичный символ + шестнадцатеричный символ // и значением символа, заключённым в пробелы и двойные дефисы document.cookie=` cookie${paddedIndex}=-- ${String.fromCharCode(i)} --` ; } document.cookie='cookieUnicode=🍪'; :::text cookie007_0x07 -- localhost / Session 16 B cookie008_0x08 -- localhost / Session 16 B cookie009_0x09 -- -- localhost / Session 21 B cookie010_0x0a -- localhost / Session 16 B cookie011_0x0b -- localhost / Session 16 B (snip for brevity) cookie030_0x1e -- localhost / Session 16 B cookie031_0x1f -- localhost / Session 16 B cookie032_0x20 -- -- localhost / Session 21 B cookie033_0x21 -- ! -- localhost / Session 21 B cookie034_0x22 -- " -- localhost / Session 21 B cookie035_0x23 -- # -- localhost / Session 21 B (snip for brevity) cookie042_0x2a -- * -- localhost / Session 21 B cookie043_0x2b -- + -- localhost / Session 21 B cookie044_0x2c --,-- localhost / Session 19 B (snip for brevity) cookie044_0x5c -- \ -- localhost / Session 19 B
Поскольку Safari прекращает обработку конкретного куки, как только встречает недопустимый символ, легко понять, что допустимы 0x09 (горизонтальная табуляция), 0x20 (пробел), 0x22 (двойные кавычки) и 0x5C (обратный слэш), но недопустимы 0x7F (удаление), 0x80-FF (символы из верхней части таблицы ASCII / Юникод).
В отличие от Firefox и Chrome, где соблюдается приведённая в RFC инструкция «отбросить эти шаги и полностью проигнорировать куки» в случае, когда в попавшемся куки содержатся управляющие символы, браузер Safari не игнорирует такой куки, а принимает часть этого значения вплоть до управляющего символа.
Как ни странно, эти исследования навели меня на причудливый баг Safari: если задать куки со значением — , — , то, по-видимому, пробелы по обе стороны от запятой убираются.
Стандартные библиотеки
Golang
Начнём с кода для работы с куки в Golang: именно в нём я впервые столкнулась с описываемой проблемой.
// sanitizeCookieValue производит из v допустимое значение куки. // https://tools.ietf.org/html/rfc6265#section-4.1.1 // // Ослабляем это требование, поскольку пробелы и запятые в значениях куки очень распространены, но // заключаем готовое значение куки в кавычки тогда и только тогда, когда в v содержатся // запятые или пробелы. // Подробное обсуждение см. в https://golang.org/issue/7243 func sanitizeCookieValue(v string) string { v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) if len(v) == 0 { return v } if strings.ContainsAny(v, " ,") { return `"` + v + `"` } return v } func validCookieValueByte(b byte) bool { return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' }
В Golang почти буквально соблюдается формулировка из RFC о том, как серверы должны обращаться с Set-Cookie, лишь с той разницей, что допускаются символы 0x20 (пробел) и 0x2C (запятая), поскольку на практике они повсеместно встречаются в куки.
Вы уже представляете, с какими препонами приходится иметь дело в разных языках программирования. Код должен одновременно и получать значения из браузеров в соответствии с разделом 5, но при этом и отправлять куки именно так, как это описано в разделе 4.1.1.
Последствия у этого могут быть самые серьёзные, и в этом можно убедиться, выполнив следующий код:
package main import ( "fmt" "net/http" ) func main() { rawCookies := ` cookie1=foo; ` + ` cookie2={"ginger":"snap","peanutButter":"chocolate chip","snicker":"doodle"}; ` + ` cookie3=bar` header := http.Header{} header.Add("Cookie", rawCookies) request := http.Request{Header: header} fmt.Println(request.Cookies()) }
Дающий такой скромный вывод:
[cookie1=foo cookie3=bar]
Незаметно отбрасываем куки, принимаемый всеми основными браузерами, и не сопровождаем код никаким исключением, из которого было бы понятно, что это произошло. Правда, если можно отбросить куки, непонятные браузеру — и опять без каких-либо побочных эффектов, то на практике всё будет гораздо лучше, чем могло бы.
PHP
Во многих языках, например в PHP, не предусмотрены нативные функции для разбора куки. Поэтому становится непросто с определённостью сказать, что в этом языке разрешено, а что нет.
При этом можно задать куки, воспользовавшись следующим кодом — и посмотреть, как отреагирует PHP:
[0x09, 0x0D, 0x10, 0x20, 0x22, 0x2C, 0x5C, 0x7F, 0xFF].forEach(i => { let paddedIndex = i.toString().padStart(3, '0') + '_' + '0x' + i.toString(16).padStart(2, '0'); document.cookie=` cookie${paddedIndex}=-- ${String.fromCharCode(i)} --` ; }); document.cookie='cookieUnicode=🍪';
Вывод
cookie009_0x09: -- -- cookie009_0x10: -- -- cookie009_0x0d: -- -- cookie032_0x20: -- -- cookie034_0x22: -- " -- cookie044_0x2c: -- , -- cookie092_0x5c: -- \ -- cookie255_0x7f: -- -- cookie255_0xff: -- ÿ -- cookieUnicode: 🍪
Когда приходится иметь дело с управляющими символами, PHP действует совершенно нелогично. Все значения из диапазона 0x00-0x09 работают нормально, равно как и 0x0D (возврат каретки), но если вы воспользуетесь 0x10 (освобождение канала) или 0x7F (удаление), то PHP без вариантов выдаст ошибку 400 Некорректный запрос.
Python
import http.cookies raw_cookies = ( 'cookie1=foo; ' 'cookie2={"ginger":"snap","peanutButter":"chocolate chip","snicker":"doodle"}; ' 'cookie3=bar' ) c = http.cookies.SimpleCookie() c.load(raw_cookies) print(c)
Вывод:
>>> Set-Cookie: cookie1=foo
Python незаметно отменяет загрузку дополнительных куки внутри SimpleCookie.load(), как только ему попадается любой непонятный куки. Это может быть очень опасно; только представьте себе, что в поддомене вполне может быть задан куки для базового домена — и из-за этого полностью нарушатся все куки для всех доменов конкретного сайта.
Путаница только нарастает, когда приходится иметь дело с управляющими символами:
import http.cookies for i in range(0, 32): raw_cookie = f"cookie{hex(i)}={chr(i)}" c = http.cookies.SimpleCookie() c.load(raw_cookie) for name, morsel in c.items(): print(f"{name}: value: {repr(morsel.value)}, length: {len(morsel.value)}")
Вывод:
>>> cookie0x9: value: '', length: 0 >>> cookie0xa: value: '', length: 0 >>> cookie0xb: value: '', length: 0 >>> cookie0xc: value: '', length: 0 >>> cookie0xd: value: '', length: 0
Как видите, здесь Python незаметно отбрасывает множество куки с управляющими символами и неправильно загружает другие. Обратите внимание: если попытаться оградить эти значения по принципу:
raw_cookie = f"cookie{hex(i)}=aa{chr(i)}aa"
то не загрузится ни один куки с управляющими символами. Вообще Python крайне непоследователен и непредсказуем в том, что касается загрузки куки.
Ruby
require "cgi" raw_cookie = 'ginger=snap; ' + "cookie=chocolate \x13 \t \" , \\ \x7f 🍪 chip; " + 'snicker=doodle' cookies = CGI::Cookie.parse(raw_cookie) puts cookies puts cookies["cookie"].value() puts cookies["cookie"].value().to_s()
Вывод:
{"ginger"=>#<CGI::Cookie: "ginger=snap; path=">, "cookie"=>#<CGI::Cookie: "cookie=chocolate+%13+%09+%22+%2C+%5C+%7F+%F0%9F%8D%AA+chip; path=">, "snicker"=>#<CGI::Cookie: "snicker=doodle; path=">} chocolate " , \ 🍪 chip cookie=chocolate+%13+%09+%22+%2C+%5C+%7F+%F0%9F%8D%AA+chip; path=
В библиотеке Ruby для работы с куки, по-видимому, многое разрешено. Язык на этапе разбора принимает любые символы, а затем, подтягивая их из хранилища куки (cookie jar) кодирует их в процентном представлении.
Возможно, это оптимальное поведение (если допустить, что подобный вариант может сосуществовать с куки), но я определённо могу вообразить случаи, в которых при задании куки через document.cookie мы совершенно не ожидаем получить их обратно уже в процентном представлении.
Rust
use cookie::Cookie; fn main() { let c = Cookie::parse("cookie=chocolate , \" \t foo \x13 ñ 🍪 chip;").unwrap(); println!("{:?}", c.name_value()); }
Вывод:
("cookie", "chocolate , \" \t foo \u{13} ñ 🍪 chip")
В Rust по умолчанию не предоставляется никаких возможностей обработки куки, поэтому за ними приходится обращаться в популярный пакет cookie. По-видимому, в конфигурации по умолчанию Rust — самый либеральный из всех языков программирования в этом отношении. Ему просто бросают любую строку в кодировке UTF-8, и он её принимает.
Почему всё это действительно важно при работе во Всемирной Паутине
Учитывая все эти безумные различия в поведении от браузера к браузеру и от языка к языку, тут определённо набирается материал на несколько увлекательных таблиц, но как всё это проявляется в реальной практике?
Когда мне впервые довелось обнаружить всё это на реальных примерах, я поняла, насколько мне повезло, что не случилось никакой катастрофы. Тестировщик вручную проверял обновление сторонней библиотеки и при работе наткнулся на странный набор ошибок. Если бы он специально мне на это не указал, то данное обновление — а тонкости его работы едва ли можно было отловить при автоматизированном тестировании — поступило бы в продакшен. В результате все последующие посетители сайта получали бы повреждённые куки, и их выбрасывало бы с сайта в сопровождении невразумительной ошибки. Так происходило бы до тех пор, пока мы не откатили бы обновление и не почистили все куки.
Причём такая проблема возникает именно из-за того, что спецификацию можно трактовать двояко. Подобную ошибку настолько легко допустить на миллионах сайтов, что можно считать — от тотального краха их может спасать лишь какой-нибудь стажёр. Всё это актуально не только для крошечных сайтов и экзотических фреймворков, но и для таких гигантов как Netflix, WhatsApp и Apple.
Можете сами убедиться, насколько легко допустить такую ошибку. Если тестируете какой-нибудь сайт, то просто вставьте в консоль следующий простой фрагмент кода — при этом замените .grayduck.mn на тестируемый вами домен:
document.cookie="unicodeCookie=🍪; domain=.grayduck.mn; Path=/; SameSite=Lax"
Netflix
Okta
Amazon
Amazon Web Services
Apple Support
Best Buy
eBay
Home Depot
Intuit
Outlook
Как же мы это исправили?
Стоит ли удивляться, что очень, очень сложно исправлять проблемы, заложенные в базовой спецификации, которой три десятка лет. Причём едва ли есть хорошее решение для такой проблемы.
Ранее был испробован вариант с блокированием куки на стороне браузера — и он сработал в Mozilla и Google:
-
Mozilla: баг 1797235, он же CVE-2023-5723 и баг 1797231
-
Google: баг 40061459
Но оказывается, что односторонняя блокировка достаточно сложна. Притом, что куки, содержащие иные символы, кроме ASCII, не так уж распространены (на них приходится чуть менее 0,01% всех куки), они по данным телеметрии значительно более распространены в таких странах как Аргентина, Мексика и Финляндия. В браузере Mozilla действительно внедрили настройку, на которую можно быстро переключиться (network.cookie.blockUnicode
), но она не активирована, поскольку вызывает проблемы с совместимостью между Mozilla и Chromium.
Потенциально возможно исправить эту проблему на стороне сервера, но такие изменения затронут миллионы сайтов, а большинство ошибок, возникающих из-за этой проблемы, скрыты глубоко внутри языков программирования и веб-фреймворков. Подобные проблемы можно сгладить в отдельно взятом Netflix, но у оператора среднего сайта не будет ни времени, ни компетенции, чтобы с ней справиться.
Истина такова, что как следует исправить эту проблему можно только силами рабочей группы IETF HTTP — она должна обновить спецификацию куки, чтобы документ приобрёл самосогласованность, а также строже регламентировал, как именно следует обращаться с куки в системах. (Не)допустимость символов, не относящихся к ASCII должна идентично трактоваться как на стороне сервера, так и в пользовательских агентах.
Причём, как бы то ни было, обработка куки в браузерах, языках программирования и фреймворках должна строиться явно, во многом по образцу современных стандартов W3C, таких как Политика безопасности контента. Если отвергать обработку тех или иных куки из-за того, что какие-то куки неаккуратно оформлены или неприемлемы, то можно столкнуться с разнообразными неожиданными поведениями.
Пожалуй, правильная последовательность обработки должна строиться как-то так:
• Начинаем со значения поля • Делаем разбиение по ; и ,, чтобы получить список "raw-cookie-pair". Запятая НЕ считается синонимом точки с запятой, так мы обеспечиваем комбинации заголовков, несмотря на то, что указано в разделе 3.2.2 спецификации RFC 7230. Для каждой пары raw-cookie-pair: ◦ Если в паре не содержится =, то переходим к следующей raw-cookie-pair ◦ Убираем ведущие и хвостовые пробелы ◦ Всю информацию до первого = трактуем как cookie-name-octets ◦ Всю информацию после первого = трактуем как cookie-value-octets ◦ Если cookie-value-octets начинается с DQUOTE, то: ‣ Удаляем одну DQUOTE в начале ‣ Удаляем одну DQUOTE в конце, если она там есть ◦ Если полученные в результате cookie-name-octets, cookie-value-octets или оба фрагмента информации неприемлемы для сервера, то переходим к следующей raw-cookie-pair ◦ Процессы объединяются в кортеж [cookie-name-octets, cookie-value-octets] в соответствии с правилами, определёнными на сервере В дальнейшем серверам СЛЕДУЕТ обрабатывать cookie-name-octets и отклонять любые кортежи, где cookie-name-octets — не токен. В дальнейшем серверам СЛЕДУЕТ обрабатывать любые cookie-value-octets и отклонять любой кортеж, в котором cookie-value-octets содержат октеты, отсутствующие в cookie-octet.
Общая таблица
|
Упр. символы1 |
htab |
space |
dquote |
comma |
backslash |
delete |
0x80-FF |
Нет |
Нет |
Нет |
Нет |
Нет |
Нет |
Нет |
Нет |
|
Нет |
Да |
Да |
Да |
Да |
Да |
Нет |
?? |
|
|
||||||||
Firefox |
Нет2 |
Да |
Да |
Да |
Да |
Да |
Да |
Да |
Chromium |
Нет |
Нет |
Да |
Да |
Да |
Да |
Нет |
Да |
Safari |
Нет3 |
Да |
Да |
Да |
Да4 |
Да |
Нет |
Нет |
|
||||||||
Golang |
?? |
Нет |
Да |
Нет |
Да |
Нет |
Нет |
Нет |
Python |
Да5 |
Нет |
Нет |
Нет |
Да |
Нет |
Нет |
Нет |
Ruby (CGI) |
Да |
Да |
Да |
Да |
Да |
Да |
Да |
Да |
Rust |
Да |
Да |
Да |
Да |
Да |
Да |
Да |
Да |
-
Не так, как определено в RFC 5234, а как в
\x00-08
и\x0A-x1F
(напр., управляющие символы за исключением горизонтальной табуляции и удаления) -
Mozilla больше не разрешает управляющие символы в
document.cookie
по состоянию на Firefox 108 -
Не прекращать обработку и игнорировать куки в соответствии с RFC
-
По‑видимому, в некоторых случаях удаляются пробелы, обрамляющие запятую
-
Иногда допускаются
0x0A-0D
, но их не удаётся сохранять в значении куки, в других случаях обработка прекращается.
Благодарности
Эта статья никогда бы не состоялась без всесторонней помощи со стороны коллег. Хотела бы поблагодарить:
-
По-Нинь Цзэна за то, что помог мне разобраться в этой проблеме
-
Дэна Ведитца из Mozilla за его исчерпывающие знания и бесконечную любезность
-
Фредерика Брауна за полезную обратную связь на раннем этапе работы
-
Стивена Бинглера из Google за настойчивость, благодаря которой описанная проблема была устранена
-
Питера Боуэна за его мысли о том, как следовало бы правильно организовать обработку куки
-
Криса Палмера и Дэвида Шинази за вдумчивую вычитку текста
-
Штефана Бюлера, который уже более десяти лет назад обнаружил некоторые описанные здесь явления.
ссылка на оригинал статьи https://habr.com/ru/articles/861300/
Добавить комментарий