Как Unicode уничтожает большинство ваших предположений о том, как на самом деле работают строки



Когда речь идет о написании чего-то простого, мы, программисты, обычно действуем интуитивно. В случае с простыми вещами мы полагаемся на четкий набор предположений вместо конкретных знаний о том, как эти вещи работают. Например, мы предполагаем, что если b = a + 1, то b больше a, или что если мы применим функцию malloc для какого-то буфера, то получим необходимое количество памяти для записи. Мы не заглядываем в документацию всякий раз, когда имеем дело с мелочами.


Мы делаем так, потому что тотальная проверка замедлит работу. Однако если бы мы все-таки провели проверку, мы бы обнаружили, что обычно ошибаемся в своих предположениях. Существует арифметическое переполнение, в результате которого a + 1 может быть значительно меньше, чем a. Иногда malloc дает нам null вместо буфера и мы оказываемся в пролете.


Нам обычно приходится обжечься на таких вещах, чтобы хотя бы немного изменить свои предположения. И даже тогда мы обычно исправляем их весьма условно.
Столкнувшись с досадной ошибкой переполнения, мы можем скорректировать свое предположение о целых числах в виде «a + 1 больше a, если отсутствует вероятность, при которой a представляет собой очень большое число». И мы действуем исходя из этого, вместо того, чтобы обдумать четкие правила, по которым работает переполнение.


Уточненные предположения – это опыт. Чаще всего они позволяют нам работать быстрее и правильнее. Однако мы можем вообще переместить некоторые вещи, например, правильную обработку malloc, из нашей внутренней категории «простые вещи» во внутреннюю категорию «сложные вещи». И тогда мы действительно можем пойти и уточнить, как они работают.


О строках


Во-первых, строки – это архетипический пример «простой вещи». Вероятнее всего, будучи детьми, мы учили буквы и цифры, и они кажутся нам очень знакомыми. Во-вторых, во время обучения программированию большинство из нас выполняло множество заданий с использованием строк, поскольку они являются практически единственным интересным встроенным типом данных в большинстве языков. Когда мы используем строки при программировании, мы вполне уверены в том, как они работают. В-третьих, мы можем иметь немало предположений насчет функционирования некоторых простых наборов символов, таких как ASCII или ISO-8859-1.
Либо потому что это мы настолько старые, либо наши учителя были настолько старыми. Это же наборы символов из тех времён, когда всё было просто!


Univac 1050-II, 1964, first computer using ASCII (wikipedia)
Univac 1050-II, 1964, первый компьютер, использующий ASCII (wikipedia).
Источник: https://upload.wikimedia.org/wikipedia/commons/thumb/5/5b/UNIVAC_1050-II.jpg/1280px-UNIVAC_1050-II.jpg


Однако на самом деле строки – это очень сложная вещь. Сравните их, например, с обычным типом Int, которую вы найдете в любом языке. Мы знаем и понимаем внутреннее представление: 64 бита, дополнительный код (ну или можем потратить 15 минут и прочитать о нём в Википедии), и понимаем его семантику (ведет себя как число, кроме случаев, когда оно слишком большое или слишком маленькое). В случае со строками мы обычно знали представление (один байт на символ, см. символ в таблице ASCII), но практически никогда не знали семантику. Наша строка могла содержать имя нашего клиента. Она могла содержать число, кусок JSON или даже SQL-запрос.


Строки – это универсальный тип Any для чего угодно, и есть вероятность, что если для какого-либо элемента программы отсутствует готовое представление, то он будет храниться и обрабатываться как строка. Независимо от того, какую типизацию вы используете – динамическую или статическую – это сводит всю безопасность типов к нулю. Положение усложняется тем, что многие вещи, для которых мы используем строки, чертовски опасны, например, SQL или HTML. По этой причине SQL-инъекции и межсайтовый скриптинг год за годом возглавляют списки уязвимостей.


Но мы хотя бы понимаем, как работают строки, верно? Мы знаем, как склеивать, менять регистр и так далее, да?


Unicode


Сегодня понять, что такое строки, значительно сложнее, чем это было в 2000 г. Переход к Unicode происходит уже несколько десятилетий, и я уже несколько лет не слышал жалоб на то, что их символы отображаются неправильно. Их печать – другое дело. Надеюсь, эта проблема будет решена в 22-м веке.


Хотя Unicode прекрасен во всех остальных отношениях, он эффективно уничтожает большинство наших «полезных» предположений о том, как действительно работают строки, но об этом мало говорится. И, к сожалению, многие из нас, скорее всего, все еще работают с устаревшими предположениями об устройстве строк. И к тому же многие из нас также больше не понимают представление строк в памяти. Признаться, я и сам не понимаю, правда.


Разрушенные предположения


Давайте сейчас пройдемся по некоторым из моих устаревших предположений, которые мне пришлось отбросить вместе с набором символов ISO-8859-1. Конечно, это не исчерпывающий перечень, но, надеюсь, его будет достаточно, чтобы выкинуть (Unicode)-строки из вашего воображаемого ящичка с «простыми вещами»


Символ представляется с помощью одного байта


В старые добрые времена ASCII каждый символ занимал свои семь битов, в результате чего было легко определять размер буфера и сканировать память. В случае с Unicode это ужасное предположение. Давайте рассмотрим один условный пример, чтобы доказать это.


В некоторый момент времени разработчики Wordpress боролись с внедрением кода SQL. Например, они пытались исправить проблему с добавлением нежелательных одинарных кавычек в вводимые пользователем данные и последующее повреждение базы данных. Что-то вроде такого выдуманного примера:


select 1 from accounts 
where user = '%s' 
    and password = '%s'

↓↓ (Пользователь использует "whocares' or true — " в качестве пароля)


select 1 from accounts 
where user = 'Avery' 
    and password = 'whocares' or true -- '
-- And now everyone can log in as Avery!

Итак, самый простой способ решения этой проблемы, какой можно представить, – это правильно закодировать одинарную кавычку во входных данных. (Но это просто только в воображении. Не делайте так.) То есть каждая одинарная кавычка ' должна быть закодирована как \' или одинарная кавычка с обратной косой.


Тогда PHP-разработчики написали функцию addslashes, и какое-то время все было хорошо. Единственной проблемой было то, что они предусмотрели экранирование байт за байтом, а не символ за символом. Разработчики не заметили эту проблему еще и потому, что они работали только с однобайтовыми символами Unicode (по большей части прежним ASCII). Затем кто-то сообразил, что если вы внесете в систему такую строку, как "뼧 or true -- ", то снова получите SQL-инъекцию.


Чтобы понять, почему это происходит, давайте посмотрим, как эти символы представлены в Unicode:


код символ
0xbf27
0xbf5c
0x27 '
0x5c \

Фактически функция addslashes заменяла все значения байтов 27 на байты 5c 27. Таким образом, "뼧 or true -- " превратилось в "뽜' or true -- ", после чего снова появились инъекции.


Нетрудно представить другие подобные катастрофы.


Длина строк – это нечто устойчивое


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


Во-первых, в качестве примера обычной операции над строкой – выполняется ли length(x) = length(toUpper(x)) для x в Unicode? Нет, поскольку в Unicode, помимо прочего, есть символы лигатуры, такие как , которые увеличиваются вдвое до FI.


Второй пример относится к нормализации. Поскольку для одного символа существует множество кодовых точек, Unicode заставляет вас производить нормализацию, чтобы, например, не оказалось двух пользователей с одинаковыми именами. Можно предположить, что нормализация или процесс выбора канонического представления для некоторого набора символов не повлияет на число нормализованных символов, однако это происходит: единый символ увеличивается в 18 раз до صلى الله عليه وسلم.


Таким образом, возможно, не стоит делать никаких предположений насчет длины строк после какой-либо операции.


Верхний и нижний регистры каким-то образом связаны


Нам, жившим с вариантами ASCII, свойственно часто использовать операции с верхним и нижним регистром. Помимо того, что теперь они могут менять длину строк, существуют и другие опасности. Важнее всего то, что прежнее предположение об уникальной связи букв верхнего и нижнего регистра ушло.


В Unicode в результате конвертации строки в верхний регистр можно потерять больше, чем просто информацию о том, в каком регистре были символы. Например, если вы переведете в нижний регистр символ кельвина , вы получите обычный символ k в нижнем регистре, без возможности обратного перевода. Это имеет на удивление большое значение при выполнении нечувствительных к регистру сравнений, поскольку toLower('K') == toLower('k'), но toUpper('K') != toUpper('k').


Почему они называются буквами верхнего и нижнего регистра: см. происхождение термина 'верхний регистр'. (wikipedia)
Почему они называются буквами верхнего и нижнего регистра: см. происхождение термина 'верхний регистр'. (wikipedia)
Источник: https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Upper_case_and_lower_case_types.jpg/800px-Upper_case_and_lower_case_types.jpg


Пробел – это 0x20


Это предположение по-прежнему верно. Байт 0x20 представляет пробел в Unicode. Однако то же самое делают U+2000, U+2001, U+2002 и многие другие, в том числе знак пробела нулевой ширины U+FEFF. Пробельный символ (whitespace) является особенным. Мы не можем использовать такие имена, как "TheAlex" и "TheAlex " одновременно, так как HTML не покажет такой пробел, и остальные пользователи не увидят разницы. Поэтому перед обработкой мы должны удалить пробел в начале и в конце.


И вот здесь Unicode дает возможность здорово пролететь. Всего-то надо в одном месте кода забыть о разнообразии пробелов и мы получим ненормализованные данные в своей базе данных. И тогда местами возникают проблемы.


Символы выглядят по-разному


В отличие от ASCII Unicode содержит множество кодовых значений для одного и того же символа и множество символов, которые выглядят почти или абсолютно одинаково, но не являются одним и тем же символом. В качестве конкретного примера вставьте "tyрeablе" == "typeable" в ваш любимый REPL. Пригодится repl.it, если у вас ничего нет под рукой.


Получили False? Это из-за того, что «р» здесь – не латинское «р», а русская буква.


Чтобы пояснить, почему это является проблемой, давайте используем этот кусок нашей схемы базы данных в качестве примера:


"uniq_address" UNIQUE CONSTRAINT, btree (country, city, address)
"uniq_name" UNIQUE CONSTRAINT, btree (name)

Могу предположить, что в эпоху Unicode эти ограничения вообще не имеют смысла. При вводе пользовательской информации пользователь может свободно подделать любой адрес или имя, какое ему угодно. Это позволяет пользователю попытаться осуществить любую кражу, используя, скажем, такое же имя, как у какого-нибудь другого пользователя. Кроме того, такие вещи, как адреса, не остаются только цифровыми. Рано или поздно адрес прочитают или напечатают, и тогда различие, видимое для базы данных, исчезнет. Существует ли в вашем процессе что-либо аналоговое, что можно использовать, притворившись другим пользователем?


Конечно, эта проблема появилась раньше, чем Unicode, особенно в некоторых наборах символов, таких как ISO-8859-5. Однако Unicode усугубляет ее серьезность и масштаб. Получается, вы не можете делать практически никаких предположений о том, как строка будет выглядеть.


Текст пишется слева направо


И что будет, если я скопирую это в свой терминал?


‮rm -rf your_home_directory # dlrow olleh ohce


Попробуйте сами. Если вы беспокоитесь о домашнем каталоге, вы можете использовать какое-нибудь простое поле ввода текста вместо своего терминала.


В некоторых языках текст не пишется слева направо, и чтобы учесть это, Unicode использует эти коды для 'смены направления письма'. Фактически текст тот же, несмотря на то, что он пишется справа налево, поэтому ваш терминал может попробовать стереть файлы, если бы вы попробовали использовать мой пример.



В урду текст пишется справа налево.


Помимо розыгрышей коллег в Teams, это двухстороннее письмо часто используется для фальсификаций, самой популярной из которых уже давно является запись длинных URL наоборот, чтобы они выглядели безвредными.


Строки декодируются одинаково


Одно из предположений насчет ASCII (и его вариантов) заключалось в том, что декодирование является тривиальной задачей, ошибки в которой маловероятны. Некоторые из моих коллег по университету бегло читают ASCII из шестнадцатеричного дампа! Это значило, что единственная проблема при передаче данных в виде строк заключалась в правильном парсинге их содержимого.


Unicode, как многобайтовая кодировка, добавляет еще один шаг. Сначала нужно пропарсить строки, а затем приступать к контенту.


Нужно сказать, что парсинг – это трудная область, известная способностью вызывать проблемы безопасности. Одна из основных проблем состоит в том, что одна и та же строка может парситься по-разному в разных программах. Хороший современный пример – когда санитайзер html (штука, которая останавливает XSS) говорит на диалекте HTML, слегка отличающемся от того, на котором говорит браузер пользователя. Если эти элементы по-разному интерпретируют какую-либо строку, санитайзер может решить, что в ней нет скриптов и другого вредоносного кода, в то время как браузер будет иметь несколько иную интерпретацию и начнет исполнять элементы входных данных как скрипты. Использование одного и того же канала для команд и контента равносильно добавлению null в языки программирования – а это ошибка на миллиард долларов!


Unicode усугубляет эту проблему, так как не все Unicode-парсеры одинаково трактуют все совокупности байтов. В основном, по-разному обрабатываются недопустимые Unicode-последовательности. Например, "e3 80 22" – это неправильная Unicode-последовательность, и один Unicode-парсер может расценить ее как недопустимый символ, тогда как другой парсер может быть менее строгим и интерпретировать ее как три символа: ã, \x80 и ". Таким образом, в веб контексте последний символ может стать проблемой, поскольку он может позволить XSS пройти через значения атрибутов.


Мысли в заключение


Для меня, как для инженера-программиста, Unicode создает много сложностей, большинство из которых мне не нужны. Перечисленные выше подводные камни можно обойти по отдельности без особого труда, но их присутствие может оказать существенное влияние на систему в целом. Таким образом, вы должны решить, какие виды строк вы допускаете в своей системе, придумать, как правильно их нормализовать, как устранить омоглифы и убрать начальные и конечные пробелы.


Проблема здесь в том, что все такие шаги должны происходить единообразно. Если вы нормализуете строку определенным образом в одной части своей программы, а в другой части это происходит по-другому, вы получаете противоречие или – в худшем случае – проблему безопасности. Вы также должны это учитывать, потому что, увы, ошибки случаются, и стараться точно записывать все, что вы делали с каждой строкой, чтобы можно было это учесть при их использовании.


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


Дополнение: писатель из меня так себе, поэтому я чувствую, что стоит еще раз подчеркнуть исходную мысль, а то вы подумаете, что я какой-то фанат ASCII.


Некоторые считают, что здесь приводятся доводы против Unicode, но это не так. Я не хочу возвращаться к ISO-8859-1, потому что это отстой. Я также готов мириться с большими сложностями, которые позволяют людям правильно записывать свои имена. Здесь я только пытаюсь доказать, что работа c Unicode неизбежно связана с бо́льшими трудностями, чем работа с ASCII. И при этом я вижу, что в отношении обработки строк люди имеют кучу убеждений со времен ASCII, не работающих с Unicode.


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


(Кроме того, в первом примере представьте, что UTF-1 и PHP не имеют строк с завершающим нулем)


Вам может быть интересно:


  1. 8 «забавных» вещей, которые могут произойти без защиты от CSRF-атак
  2. Haskell – хороший выбор с точки зрения безопасности ПО?
  3. Property-based тестирование с QuickCheck
  4. В чем польза формальных спецификаций вроде OpenAPI?

Комментарии (40)



  1. NeoCode
    06.09.2021 20:10
    +27

    Дизайн Unicode по большому счету содержит множество ошибок. Из за желания угодить всем нагородили вот это всё, и это при том что Unicode создавался во времена «однополярного мира», когда США и американские корпорации, будучи практически монополистами в сфере IT-стандартов и технологий, вполне могли продавить более простую концепцию.

    Зачем например в Unicode «направление письма»? Unicode это просто набор символов. Как их вводить, выводить и интерпретировать — дело софта. Вот в Китае и Японии направление письма вообще «сверху вниз», но ведь Unicode такое не поддерживает? И кстати, китайцы и японцы с появлением компьютеров вполне адаптировались к направлению «слева направо», может и арабы бы также адаптировались?

    Далее, регистр символов — это особенность исключительно европейских языков, унаследованная из ASCII (который создавался в условиях гораздо более жесткой экономии оперативной памяти). Понятно что старались сделать обратную совместимость, но по большому счету регистр должен был стать «декорацией» (как bold, italic, subscript или superscript), а не отдельными кодовыми точками. Символы 'a' и 'a' (курсивом) — как-бы один символ, хотя они совершенно отличаются визуально. Символы 'a' и 'A' при этом — разные. Логика?

    Из-за той же обратной совместимости осталась незадействованной большая часть символов в самом начале кодовой таблицы, с 0x00 до 0x1F. А между тем их стоило бы задействовать для наиболее употребительных спецсимволов (таких например как стрелки, дополнительные знаки пунктуации, некоторые математические символы и т.п.). Размещение этих символов в однобайтовой части важно, так как именно однобайтовая часть Unicode по очевидным причинам нанесена на все клавиатуры мира. Оставить в этой области можно было бы только нулевой код и код новой строки, и заодно провести редизайн концепции переноса строки, т.к. в основных системах (Win, Mac, Linux) перенос строки устроен по-разному (0x0D, 0x0A или оба сразу).

    Ну и наверное можно еще накопать таких вот нелогичностей.


    1. ToSHiC
      06.09.2021 20:57
      +1

      Как тогда поступить с ß в немецком, например?


      1. NeoCode
        06.09.2021 21:45
        +8

        Не знаком с немецким, но почитал — занимательно, получается что это как раз тот случай, когда Unicode повлиял на язык (введение заглавной буквы в Unicode раньше чем она появилась в языке).
        Unicode — это прежде всего набор символов и соответствующих им кодов. Как их использовать — дело софта. И наверное, если разные модификации (декорации) одного смыслового символа значительно отличаются и не могут быть сгенерированы программно (как в случае underline, subscript, superscript — просто подчеркиванием или уменьшением размеров), то нужно эти символы вводить отдельно…

        Одинаковые символы с разными кодами (как например латинское 'A' и русское 'А') введены просто для удобства группировки, чтобы символы одного языка не были разбросаны по всему диапазону. Для европейских языков большая часть символов общая, и лишь некоторые (2..4 штуки в каждом алфавите) свои собственные — поэтому не имеет смысла вводить отдельно коды для «немецких», «французских» и «итальянских» букв 'A', а проще сформировать единый общеевропейский метаалфавит, что и было сделано. В русском большая часть символов отличается, поэтому сделали отдельный набор.

        В общем понятно, что Unicode несет на себе бремя обратной совместимости не только с ASCII, но и с историей письменности как таковой:) И идеального решения не найти. Может быть нужно было радикальное решение в виде разделения абстракций — отдельно слой сопоставления символов и «кодов символов» (в этом слое все буквы, выглядящие как 'A', будут иметь единый код), и отдельно слой сопоставления «кодов символов» и «смысловых кодов» (здесь уже разделение по алфавитам). В результате было бы две кодировки — графическая и семантическая. Понятно, что это кардинально повлияло бы на все ОС и на весь софт, и мы бы жили в совсем другом мире:)


        1. ToSHiC
          06.09.2021 23:28
          +20

          В результате было бы две кодировки — графическая и семантическая.

          Так они есть. Можно сделать букву "й" так, как она написана левее, а можно и так: "й". Вы не можете заметить это глазом (конечно, если ваш браузер правильно всё делает), но второй вариант состоит из двух глифов: "и" и закорючка над ней. Вот статья на хабре про это: https://habr.com/ru/post/262679/ . А теперь, собственно, сложная часть: вы не можете в общем случае сделать upper/lower case на такие составные глифы. Потому что, например, в турецком языке есть строчная буква ı, которая получается из заглавной I. А для английского языка lowercase(I) == i.

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

          Всё же создатели unicode пользовались концепцией, что письменность уже есть, и для каждого народа его письменность ему дорога, и нужно создать систему, которая позволить описать все их символы в более-менее логичном и единообразном виде, предоставив ещё и механизмы для каких-то общих операций типа lowercase и uppercase, а так же нормализации. То есть идти от предметной области, а не пытаться насаждать что-то новое и неестественное для каждого конкретного языка.


          1. NeoCode
            07.09.2021 00:12
            +2

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

            С одной стороны это конечно хорошо, что можно генерировать некоторые новые символы таким образом. А с другой… как-то бессистемно. Ну ввели бы тогда специальный код «escape» для специальных escape-последовательностей — и вот туда можно было бы добавить элементы форматирования. В том числе жирный/курсив/подчеркивание, капитель, верхний и нижний индекс, надстрочные и подстрочные символы (Ruby character), геометрические преобразования (поворот символа на 90/180/270 градусов или даже повороты с каким-то шагом типа 5°, отражение по горизонтали и вертикали), цвет символов (для всяких эмодзи вполне актуально, и сейчас это уже как-то делают) и т.п. Это было бы хотя-бы системно. Но тогда бы это была уже не таблица символов, а нечто большее. А сейчас оно застряло где-то на полпути…


            1. dikey_0ficial
              07.09.2021 07:53
              +5

              если одну закорючку присоединить к другой, получится zalgo text


              1. danfe
                07.09.2021 09:46
                +9

                В русском разделе такой статьи нет, но странно, что в английской версии не упомянут один из самых удачных мемов, на мой взгляд. :-)


            1. nin-jin
              07.09.2021 10:56
              +1

              И получится хтмл.


              1. NeoCode
                07.09.2021 14:29
                +1

                Да ладно бы html, они и полноту по Тьюрингу могут сделать:)))


                1. edo1h
                  08.09.2021 01:35

                  ну для схожей задачи (печати на принтер) уже один раз сделали же )


        1. naishin
          07.09.2021 17:48

          В результате было бы две кодировки — графическая и семантическая.

          Так и есть. Для рендеринга Unicode строки, требуется множество преобразований. Кодепоинты отдельно, глифы отдельно. В прошлом проекте рендерингом занимался мой коллега, он много и интересно рассказывал про юникод и связанные проблемы. К сожалению, подробностей не помню, но тип Строка содержал пять разных представлений. 20 байт на один отображаемый символ.


    1. BabrHabr
      07.09.2021 09:57
      +4

      Очень смелое предположение приравнять заглавные буквы к стилю типа italic или bold. Тут же оказывается что такой стиль как "заглавная" можно применить далеко не ко всем символам. Сразу получается нестыковка. Несемметричность. А хотели сделать как лучше


      1. murzilka
        07.09.2021 10:03

        Интересно, а italic разве можно безопасно применять к иероглифам? Это не может исказить их смысл?


        1. BabrHabr
          07.09.2021 11:00
          +2

          таким образом получается что универсальный подход невозможен даже к стилям написания типа italic/bold не говоря уже про строчная/заглавная


      1. FreeNickname
        07.09.2021 11:57
        +1

        Не говоря уже о том, что italic / bold – это просто оформление, а заглавная буква семантически отличается от строчной.


        1. vlad-kras
          08.09.2021 14:40
          +1

          заглавная буква семантически отличается от строчной

          Особенно в аббревиатурах. «Пишем оду ОДУ». ОДУ — обыкновенное дифференциальное уравнение

          Символы 'a' и 'a' (курсивом) — как-бы один символ, хотя они совершенно отличаются визуально. Символы 'a' и 'A' при этом — разные. Логика?

          Стиль символа - это всего лишь оформление. Оно только изредка может быть использовано для отображения выделения интонацией или других нюансов произношения. Чаще всего для книг это курсив, иногда жирный шрифт или разреженные буквы. Для компьютерной переписки чаще в качестве выделения используем КАПС. Если взять несколько текстов с полностью одинаковым содержанием, но 1й будет написан обычным шрифтом, 2й - весь исключительно курсивом, а 3й - полностью буквами красного цвета, то для читателя смысл всех текстов совершенно одинаков, хотя оформлены они по-разному.

          Заглавные и строчные буквы - это различие другого рода. Например, следующие фразы отличаются только регистром всего одной буквы, но несут разный смысл. «Он позвонил жене» и «Он позвонил Жене»

          В первом случае, очевидно, речь идёт о супруге. Во втором случае Женя - это имя, причём необязательно женское.


          1. nin-jin
            08.09.2021 15:34

            Он позвонил жене - это уже речь о любимой работе. В принципе, любое изменение стиля (в том числе и начертания) делается с каким-либо смыслом. И тут мы возвращаемся к тегам strong, abbr, irony..


            1. FreeNickname
              08.09.2021 15:46

              Русский язык на уровне языка не имеет курсива, полужирного и т.д. А вот заглавные буквы имеет.

              Он позвонил жене - это уже речь о любимой работе. 

              Ну вот я носитель языка, я вас совершенно не понял, даже с вашим пояснением. А вот жене/Жене – вполне очевидно и однозначно.


              1. nin-jin
                08.09.2021 16:52

                Это русская пунктуация, по очевидным причинам, не имеет. А вот русская типографика очень даже имеет.

                Находясь в контексте даже вы бы всё поняли. А без контекста и вашу "Жене" можно трактовать, не как имя, а как особо почтительное обращение к супруге. Так что для ясности надо ещё и ударение добавить.


                1. vlad-kras
                  08.09.2021 18:16

                  В моем первом сообщении жирное начертание - просто для визуального выделения различий. Про «strong, abbr, irony» не понял, предложения «Он позвонил ...» содержали информацию только про факт звонка.

                  Находясь в контексте даже вы бы всё поняли.

                  Правильное написание в данном случае позволяет обходиться без контекста и понять однозначно исходя только из правил языка. А так «без контекста» даже если я напишу «позвонил супруге» вам всё равно не будет понятно какой именно - официально расписанной или гражданской, настоящей или бывшей, а может там вообще многожёнство.

                  А без контекста и вашу "Жене" можно трактовать, не как имя, а как особо почтительное обращение к супруге

                  Это по каким правилам русского языка? Правила по «вы» и «Вы» существуют и там в одном из случаев большая буква - как часть канцелярского протокола. Знаю про Человека с большой буквы, а с большой буквой для жены ничего не встречал.


                  1. nin-jin
                    08.09.2021 18:26

                    Знаю про Человека с большой буквы

                    По тому же самому правилу.


    1. nin-jin
      07.09.2021 10:52

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


      1. unsignedchar
        07.09.2021 11:45
        +2

        Тогда не направление, а код локали нужно сохранять. Раз уж в разных локалях upper/lower case работают по разному — такую составную строку правильно обработать невозможно.


    1. faiwer
      07.09.2021 11:40
      +5

      Зачем например в Unicode «направление письма»?

      Если бы направление письма касалось всегда всего текста в целом, вы, возможно, были бы правы. Но ведь направление гуляет в пределах отдельно взятого приложения. Так что это особенность именно письма а не интерпретации цельного текста.


      но по большому счету регистр должен был стать «декорацией»

      Почему? Названия и имена мы пишем с заглавных букв. Немцы пишут все существительные с заглавных букв. Англичане почти каждое слово в заголовках. Если этой информации не будет в самой строке, то откуда её взять? Натравливать на текст сложнейшие постпроцессоры, заранее выставляя конкретный язык, которые будут глубоко анализировать каждую строку перед выводом? Утопия.


      Мне кажется главная беда UTF-8 не несуразности в кодировке, а люди. Как и в случае с временем, люди придумали избыточно сложные и часто нелогичные системы, да ещё и с таким разнообразием.


    1. Maccimo
      07.09.2021 16:31
      +5

      Символы 'a' и 'A' при этом — разные. Логика?

      Правила правописания.
      Хотя для детей, воспитанным твиттором, это может быть и не очевидно.


    1. vanxant
      07.09.2021 16:53
      +4

      может и арабы бы также адаптировались?

      Арабы может и да, мусульмане точно нет.
      Раньше переписчикам Корана за первую ошибку отрубали руку, за вторую — голову.


  1. BabrHabr
    07.09.2021 07:50

    В UTF-8 addslashes работает правильно


    1. Bedal
      07.09.2021 15:15
      +1

      зато есть ВОМ, который днесь мне доставил развлечений…


  1. Zagrebelion
    07.09.2021 10:50
    -9

    Статья автора, который вдруг узнал, что за пределами английского языка тоже есть жизнь. Или это перевод 10-летней давности?

    Представляю, как бы он удивлялся, если бы увидел селектор win-koi-dos на каждой странце сайта, или статьи "как настроить апач на 8001 порт - кодировка cp1251, а 8002 порт - cp866".


  1. Srgun
    07.09.2021 13:08
    +1

    "uniq_name" UNIQUE CONSTRAINT, btree (name)

    При вводе пользовательской информации пользователь может свободно подделать любой адрес или имя, какое ему угодно. Это позволяет пользователю попытаться осуществить любую кражу, используя, скажем, такое же имя, как у какого-нибудь другого пользователя.

    Вообще не понял данный посыл.

    Использование Unicode позволяет иметь имя пользователя (адрес и т.п.), которое выглядит на экране/при печати так же, как у другого пользователя. Но никак не позволяет залогиниться под другим пользователем либо что-то сделать от его лица в любой информационной системе.


    1. eimrine
      07.09.2021 18:20
      +1

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


      1. Srgun
        08.09.2021 09:18

        Можно, но это другой пример.

        А пример, приведённый в статье в разделе SQL, имхо, не имеет никакого смысла.


    1. tsp1000
      04.10.2021 20:57

      Адрес на посылке, отправляемой почтой, вполне можно так подделать.


  1. nickolaym
    07.09.2021 15:48
    +2

    Старая добрая турецкая И/Ы может доставить очень много боли. И доставляет, в известной операционной системе, нечувствительной к регистру в именах файлов.


  1. Urub
    07.09.2021 16:44

    Если сохранять 뼧 в файл, то это EB BC A7, а не BF 27 - почему ?
    Я могу предположить, что это изза utf8, но тогда и инъекций с этим символом не будет.

    Поправьте где неправ.


    1. BabrHabr
      07.09.2021 17:56

      Как я уже писал в UTF8 такие инъекции не работают

      В UTF16 это действительно BF 27. Но это не значит что такая иьекция будет работать всегда и везде


      1. Urub
        08.09.2021 12:15

        А есть ли в utf8 символ в 2 или более байтов, содержащий 0x27 ?


        1. BabrHabr
          08.09.2021 12:41

          нет

          но даже в UTF16 я не понимаю почему может работать такая иньекция