Текстовые редакторы, основная задача которых — отображение моноширинного шрифта (например, кода), должны, как и следует из названия, показывать символы одной ширины.
Но есть нюанс
В Unicode есть символы, видеть которые не положено. Текстовый редактор может просто отрендерить текст с таким символом, а может предпринять какие-то действия, чтобы сделать его заметным.
Кто же они?
Код | Пример | Название |
---|---|---|
U+2060 | foo?bar | WORD JOINER |
U+2061 | foo?bar | FUNCTION APPLICATION |
U+2062 | foo?bar | INVISIBLE TIMES |
U+2063 | foo?bar | INVISIBLE SEPARATOR |
U+180E | foo?bar | MONGOLIAN VOWEL SEPARATOR |
U+200B | foo?bar | ZERO WIDTH SPACE |
U+200C | foo???bar | ZERO WIDTH NON-JOINER |
U+200D | foo?bar | ZERO WIDTH JOINER |
U+FEFF | foo?bar | ZERO WIDTH NO-BREAK SPACE |
Word joiner, U+2060
Пришёл на смену zero-width no-break space (U+FEFF), потому что U+FEFF стал использоваться для кодирования BOM (byte-order mark, несколько байт в начале файла, обозначающие его кодировку и порядок байт). Этот символ запрещает перенос строки там, где он встречается.
Zero-width no-break space, U+FEFF
Устаревший символ, заменён на word joiner, использовался в тех же целях.
Zero-width joiner, U+200D
Используется в индийских и арабских шрифтах для объединения символов, которые без него не были бы соединены.
Zero-width non-joiner, U+200C
В начертаниях с лигатурами можно вставить его между буквами, чтобы лигатуры не было:
Он встречается даже на клавиатурах:
Zero-width space, U+200B
Используется, когда нужно обозначить границу слов, не вставляя пробел. Этот текст будет переноситься по словам:
Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word?Word
А этот нет:
WordWordWordWordWordWordWordWordWordWordWordWordWordWordWordWordWordWordWordWordWordWord
Invisible Operators: function application U+2061, invisible times U+2062, invisible separator U+2063
"Невидимые операторы", добавленные в Unicode 3.2. Нужны для обозначения математических операций в выражениях.
Например, эта запись: Aij
Может означать или индекс (i, j) в двумерном массиве, или индекс i*j в одномерном. Для устранения неоднозначности можно использовать или Invisible times, или Invisible separator, чтобы было понятно, что имелось в виду.
Аналогично, f (x + y), это или умножение, или функция.
Визуально они не должны отличаться, но некоторые парсеры смогут понять, что имелось в виду.
Mongolian vowel separator, U+180E
Из названия понятно, для чего он. Этот символ уже не раз вызывал проблемы. Очень хорошо описан в этом ответе.
Как это выглядит
Конечно же, отображение зависит не только от редактора, но ещё и от шрифта, посмотрим на рендеринг текста, не меняя настроек редакторов.
Atom, Sublime, VSCode, Xamarin Studio, XCode, Notepad++:
Cat не показывает их:
Но если его запустить с параметром -A
в linux или -v
в macOS, то почти все символы видны (спасибо за подсказку в комментариях):
cat -v invisibles.txt
U+2060 foo?M-^A?bar WORD JOINER
U+2061 foo?M-^A?bar FUNCTION APPLICATION
U+2062 foo?M-^A?bar INVISIBLE TIMES
U+2063 foo?M-^A?bar INVISIBLE SEPARATOR
U+180E foo?M-^Nbar MONGOLIAN VOWEL SEPARATOR
U+200B foo?M-^@M-^Kbar ZERO WIDTH SPACE
U+200C foo?M-^@?M-^@?M-^@M-^Lbar ZERO WIDTH NON-JOINER
U+200D foo?M-^@M-^Mbar ZERO WIDTH JOINER
U+FEFF foo?bar ZERO WIDTH NO-BREAK SPACE
Vim тоже не сообщает о некоторых символах, даже с включённой настройкой set list, а вот less справляется лучше:
Web
GitHub, вот так показываются эти символы в pull request-ах и diff-ах:
Один из популярных редакторов кода, CodeMirror:
В том же CodeMirror, используемом jsbin, в IE часть символов видна:
ACE догадывается, что там бяка, и говорит, что что-то тут нечисто, но вот что именно — показывает не всегда:
Редакторы кода и diff tools
Редакторы на платформе IntelliJ:
Разные инструменты сравнения кода под macOS (P4Merge, FileMerge, KDiff3):
KDiff3, попытка засчитана, но этого не достаточно.
SourceTree: не обрабатывает текст вообще никак, плохо:
Tortoise, тоже почти ничего:
git diff
: молодец, показал всё, ещё и выделил (хотя, на самом деле, сделал это less). Просто прекрасно, для diff tools это образец для подражания:
Anguish: brainfuck, которого нет
Кто-то сделал язык программирования Anguish, использующий только невидимые символы. Он основан на brainfuck, но использует не знаки пунктуации, а символы, о которых мы говорили выше. Есть даже интерпретатор на Perl и примеры использования.
Эксплуатация
Плохой код, фу таким быть, сделать закладку можно совсем просто:
function f() {
// ну вы поняли, на что заменить
return 'access_denined';
}
let code = f();
if (code === 'access_denied') {
return 401;
}
Что делать
Пиши чистый код, %username%. Следуй best practices, их придумали не просто так, а для того чтобы держать меньше вещей в голове, в том числе своевременно замечая такие штуки. Увидел магическую строчку, странный или непроверяемый default case, ещё что-то: есть время — не поленись, перепиши как надо. Проводи код-ревью, смотри что коммитят в твою репу, поддерживай хорошее покрытие. Помни, что строке может быть не только то, что видно на экране, проверь в hex-редакторе, если возникло подозрение.
Вообще, вероятность реализации бэкдора через невидимый символ, конечно, есть, но скорее нет, чем да: найти его достаточно просто, а вставить закладку в говнокод можно и другими методами.
Почитать
- Unicode Demystified, A Practical Programmer’s Guide to the Encoding Standard, by Richard Gillam (вы знаете, где искать) — хорошая книга про unicode, многое рассказано, в том числе и о таких символах
Комментарии (42)
iloskutov
08.10.2016 15:59+10Cat не показывает их
Он вообще ничего не показывает — он просто выводит содержимое файла на консоль, а она уже отображает его так, как считает нужным. По Вашему же скриншоту видно, что поведение разных терминалов несколько отличается, но cat здесь совершенно ни при чём
JIghtuse
08.10.2016 19:22+2Да, стоило его хотя бы с опцией
-A
запустить — отображает непечатаемое. Не знаю, всё ли — исходников тестов не приложено.Antelle
08.10.2016 19:34+2Исходник вот: https://gist.github.com/antelle/216aa1f34c0b9dee89e574934325d2ce
Проверил на маке, видно почти всё, только опция в cat под macos называется -v. Под linux не проверял, могу предположить, что будет так же, возможно ещё последний появится.
outputcat -v invisibles.txt U+2060 foo?M-^A?bar WORD JOINER U+2061 foo?M-^A?bar FUNCTION APPLICATION U+2062 foo?M-^A?bar INVISIBLE TIMES U+2063 foo?M-^A?bar INVISIBLE SEPARATOR U+180E foo?M-^Nbar MONGOLIAN VOWEL SEPARATOR U+200B foo?M-^@M-^Kbar ZERO WIDTH SPACE U+200C foo?M-^@?M-^@?M-^@M-^Lbar ZERO WIDTH NON-JOINER U+200D foo?M-^@M-^Mbar ZERO WIDTH JOINER U+FEFF foo?bar ZERO WIDTH NO-BREAK SPACE
Sirikid
08.10.2016 18:28+1Очевидно мы должны добавить в Unicode специальный символ для отображения невидимых символов (в случае необходимости).
Antelle
08.10.2016 18:38+1Обычно принято использовать или bullet (•), или replacement character (?), или
квадратик такойPower
08.10.2016 21:03+7А
git diff
не молодец и он ничего не выделил. Он просто раскрасил строчки целиком и скормил на входless
.
vbif
08.10.2016 23:54+1В Википедии до сих пор не могут решить, что делать с неразрывными пробелами в викификаторе: при викификации конструкции типа « » превращаются в неразрывные пробелы, а неразрывные пробелы при последующей викификации превращаются в обычные. Такое поведение автор объясняет тем, что неразрывные пробелы в редакторе не отличить от обычных, и потому надо от них избавляться. Тема поднималась уже не один раз, и все соглашались, что это — очевидная недоработка, что так делать нельзя, но отремонтировать до сих пор никто не берётся.
Crandel
09.10.2016 07:28+2Arch linux, Emacs через терминал
Скриншотequand
09.10.2016 11:49+1FreeBSD default ee editor
U+2060 fooa~A bar WORD JOINER U+2061 fooa~A?bar FUNCTION APPLICATION U+2062 fooa~A?bar INVISIBLE TIMES U+2063 fooa~A?bar INVISIBLE SEPARATOR U+180E fooa ~Nbar MONGOLIAN VOWEL SEPARATOR U+200B fooa~@~Kbar ZERO WIDTH SPACE U+200C fooA~@A~@a~@~Lbar ZERO WIDTH NON-JOINER U+200D fooa~@~Mbar ZERO WIDTH JOINER U+FEFF fooi»?bar ZERO WIDTH NO-BREAK SPACE U+0020 foo bar SPACE U+00A0 foo bar NO-BREAK SPACE U+1680 fooa~Z~@bar OGHAM SPACE MARK U+2000 fooa~@~@bar EN QUAD U+2001 fooa~@~Abar EM QUAD U+2002 fooa~@~Bbar EN SPACE U+2003 fooa~@~Cbar EM SPACE U+2004 fooa~@~Dbar THREE-PER-EM SPACE U+2005 fooa~@~Ebar FOUR-PER-EM SPACE U+2006 fooa~@~Fbar SIX-PER-EM SPACE U+2007 fooa~@~Gbar FIGURE SPACE U+2008 fooa~@~Hbar PUNCTUATION SPACE U+2009 fooa~@~Ibar THIN SPACE U+200A fooa~@~Jbar HAIR SPACE U+202F fooa~@?bar NARROW NO-BREAK SPACE U+205F fooa~A~_bar MEDIUM MATHEMATICAL SPACE U+3000 fooa~@~@bar IDEOGRAPHIC SPACE
FreeBSD default vi
U+2060 foo\xe2\x81\xa0bar WORD JOINER U+2061 foo\xe2\x81\xa1bar FUNCTION APPLICATION U+2062 foo\xe2\x81\xa2bar INVISIBLE TIMES U+2063 foo\xe2\x81\xa3bar INVISIBLE SEPARATOR U+180E foo\xe1\xa0\x8ebar MONGOLIAN VOWEL SEPARATOR U+200B foo\xe2\x80\x8bbar ZERO WIDTH SPACE U+200C foo\xc2\x80\xc2\x80\xe2\x80\x8cbar ZERO WIDTH NON-JOINER U+200D foo\xe2\x80\x8dbar ZERO WIDTH JOINER U+FEFF foo\xef\xbb\xbfbar ZERO WIDTH NO-BREAK SPACE U+0020 foo bar SPACE U+00A0 foo bar NO-BREAK SPACE U+1680 foo\xe1\x9a\x80bar OGHAM SPACE MARK U+2000 foo\xe2\x80\x80bar EN QUAD U+2001 foo\xe2\x80\x81bar EM QUAD U+2002 foo\xe2\x80\x82bar EN SPACE U+2003 foo\xe2\x80\x83bar EM SPACE U+2004 foo\xe2\x80\x84bar THREE-PER-EM SPACE U+2005 foo\xe2\x80\x85bar FOUR-PER-EM SPACE U+2006 foo\xe2\x80\x86bar SIX-PER-EM SPACE U+2007 foo\xe2\x80\x87bar FIGURE SPACE U+2008 foo\xe2\x80\x88bar PUNCTUATION SPACE U+2009 foo\xe2\x80\x89bar THIN SPACE U+200A foo\xe2\x80\x8abar HAIR SPACE U+202F foo\xe2\x80\xafbar NARROW NO-BREAK SPACE U+205F foo\xe2\x81\x9fbar MEDIUM MATHEMATICAL SPACE U+3000 foo\xe3\x80\x80bar IDEOGRAPHIC SPACE
vadimr
09.10.2016 12:57+4Интересно, сколько проблем возникает из-за невероятно актуальной предоставленной программистам возможности именовать объекты на древнемонгольском. При том, что сами монголы давно перешли на кириллицу.
a-motion
10.10.2016 09:09+1Полагаю, что существуют люди, которые изучают древнемонгольский, и эти люди, в целом, ничуть не хуже нас с вами. Я лично около двадцати пяти лет назад немало натерпелся от бородатых программистов, которые рассуждали так: «интересно, сколько проблем может возникнуть из-за невероятно актуальной предоставленной программистам возможности писать по-русски, при том, что русские отлично способны использовать транслит».
Проблемы криворуких программистов, которым в 2016 году в диковинку существование таких сущностей — должны оставаться проблемами [отныне безработных] программистов.
vadimr
10.10.2016 09:28+2Изучение древнемонгольского каким-то образом побуждает именовать на этом языке переменные в программах?
a-motion
10.10.2016 09:55-3Изучение древнемонгольского побуждает алкать возможности вводить такие символы.
Здравый смысл побуждает не использовать конструкцию
if valid(symbol) then ... else ...
в парсерах. Потому что не автору парсера решать, что мне втемяшится использовать в качестве идентификатора, это не его собачье дело (если он, конечно, достаточно квалифицирован.)
Я так именую методы, которые пишут некий «особенный» внутренний лог:
?
и?
, и я не стану пользоваться говноязыком, который мне это сделать не позволит.vadimr
10.10.2016 10:00+3Забавно Вам будет отлаживать Вашу программу с какого-нибудь удалённого терминала, не поддерживающего юникодный ввод.
a-motion
10.10.2016 10:33-3Забавно Вам будет отлаживать Вашу программу с какого-нибудь удалённого терминала,
не поддерживающего юникодный ввод.Здесь смешно все: и наличие в наше время терминалов, не поддерживающих юникод, и отладка с удаленного терминала. Оглядитесь, тут вокруг уже XXI век наступил, причем — больше пятнадцати лет тому назад. Ну и да, я умею настраивать деплой так, чтобы ржать в голос над словами «отладка с удаленного терминала».
Alex_kk
09.10.2016 18:25+1Статья полезна тем, что показывает, что есть неочевидное там, где, казалось бы, все очевидно.
А что касается практического использования Unicode в разработке, то следует помнить о «нестандартных» «пробельных» символах (см., напр., \u00A0, \u0082, \u0083, \u0085) и о символах управления потоком (RTL/LTR; см., напр., \u202A \u202B \u202C \u202D \u202E). На опыте проверено, что такие символы могут встречаться в пользовательском вводе (и, что удивительно, в системах электронного документооборота в России!). За идентификаторами в программах мы, программисты, уж как нибудь уследим. :) А вот за пользователем…
И, разумеется, следует помнить об эквивалентности графических изображений, возникающих из различных комбинаций кодов Unicode (Unicode Equivalence).
hdfan2
10.10.2016 09:23+4Вспомнилось. На приёме у логопеда:
— Доктор, у меня проблема. Я не выговариваю букву «...»!
— Какую?
— НУ, БУКВУ «...»!!!
Unk
10.10.2016 17:08+4Я решил довести идею скрытых символов до
абсурдавозможности применения и написал невидимый кодировщик
stychos
11.10.2016 12:31+1Реквестирую название указанной в топике клавиатуры.
Antelle
11.10.2016 12:39Вот отсюда: https://en.wikipedia.org/wiki/File:German-T2-Keyboard-Prototype-May-2012.jpg
Скорее всего, типографские символы — гравировка просто.grossws
11.10.2016 12:47Они через alt gr набираются, если ОС поддерживает. Т. е. в обычном режиме там используется три функции на клавишу.
Antelle
11.10.2016 12:50Набираются, но кажется, stychos интересуется, где взять клавиатуру, чтобы нарисованы именно были.
stychos
11.10.2016 12:52Да, было бы интересно иметь клавиатуру с третьим ярусом из всех непечатаемых и дополняющих символов. Кажется, это то, чего ещё нет )
Antelle
11.10.2016 12:54Закажите гравировку или печать, оно недорого стоит, могут что угодно нанести.
stychos
11.10.2016 12:56Я практически в третьем мире живу, тут не так просто сделать качественную гравировку или печать чего-либо кроме самоклеек. Хотя, наверно, китайцы сейчас могут делать штучные заказы и на эту тему — надо будет попробовать.
stychos
11.10.2016 12:54В macOS выбор богат для AltGr, но всё равно, далеко не все. Иногда бывает необходимо, например, часто вставлять символ «Indent to here».
a-motion
11.10.2016 12:57В macOS выбор богат для AltGr
Бгг. Вот моя раскладка: https://github.com/mudasobwa/huya-xkb
Кроме того, даже в MacOS можно сделать свою какую-угодно раскладку, просто, как и все остальное, немного неестественным образом.
stychos
11.10.2016 12:59Почему неестественным? Есть очень даже удобная утилитка для этого. Я просто думал, может есть готовые best practices для типографики, отлитые в опыте и железе :)
А раскладка знатная!a-motion
11.10.2016 13:03Неестественным — потому, что когда мне нужен внезапно на полчаса какой-нибудь нестандартный символ, я редактирую текстовый файл и обновляю кэш: клавиша включена. Я посмотрю, как вы утилиткой себе на часик при редактировании презентации какой-нибудь впилите нестандартный буллит.
vbif
11.10.2016 13:14Интересно, никто ещё не придумал удобных менеджеров буфера обмена, чтобы можно было хранить одновременно несколько фрагментов, и при необходимости вставлять нужный по хоткею? Помню, что-то подобное пытались в msoffice сделать.
a-motion
11.10.2016 13:23+1Как бы в Linux из коробки, в неестественных OS — не знаю, но думаю тоже кто-нибудь запилил.
stychos
11.10.2016 14:20+1В макоси также можно отредактировать на полчасика файл с ремапами, да и раскладку тоже можно ручками в текстовом редакторе отредактировать, не вижу особых отличий xml от Вашего конфига. Совсем ленивые пользуются Ukelele, вроде тоже не создаёт сложностей.
WST
Вспомнилось, как много лет назад я иногда злоупотреблял такими символами, чтобы побыстрее исчерпать трафик или квоту на объём БД у своей «жертвы». Например, пишу фразу «Привет! Как дела?», а внутри спрятаны тысячи невидимых знаков. Короткое сообщение начинает «весить» достаточно много. Правда, иногда эти символы выдают себя нагрузкой на браузер (или Jabber-клиент) — к примеру, становится «трудно» выделять текст, прокрутка подтормаживает и т.д.
kemko
Эффект видимо зависит от понимания жертвой своего jabber-клиента. У меня всегда было включено сжатие, на экономию со школьных обедов не так много можно было купить интернет-трафика.
WST
Чаще всего «жертвами» были изрядно досаждавшие попрошайством ботов арабы, у которых нередко были такие слабые телефоны, что на zlib им тупо не хватало памяти (всё ж таки, я о временах J2ME изначально говорил).
kemko
Видимо я был мажором. На Siemens CX65 zlib вполне работал.