Команда для извлечения списка символов в заданном классе символов в текущей локали

Каким может быть способ получить список всех символов в заданном классе символов (например, blank , alpha , digit …) в текущей локали.

Например,

 LC_ALL=en_GB.UTF-8 that-command blank 

в идеале, в моей системе Debian будет отображаться нечто вроде:

  09 U+0009 HORIZONTAL TAB 20 U+0020 SPACE e1 9a 80 U+1680 OGHAM SPACE MARK e1 a0 8e U+180E MONGOLIAN VOWEL SEPARATOR e2 80 80 U+2000 EN QUAD e2 80 81 U+2001 EM QUAD e2 80 82 U+2002 EN SPACE e2 80 83 U+2003 EM SPACE e2 80 84 U+2004 THREE-PER-EM SPACE e2 80 85 U+2005 FOUR-PER-EM SPACE e2 80 86 U+2006 SIX-PER-EM SPACE e2 80 88 U+2008 PUNCTUATION SPACE e2 80 89 U+2009 THIN SPACE e2 80 8a U+200A HAIR SPACE e2 81 9f U+205F MEDIUM MATHEMATICAL SPACE e3 80 80 U+3000 IDEOGRAPHIC SPACE 

И в локали C может отображаться что-то вроде:

 09 U+0009 HORIZONTAL TAB 20 U+0020 SPACE 

То есть представление символа в локали в терминах массивов байтов (например, UTF-8 в первом примере и один байт во втором), эквивалентный код символа Unicode и описание.

контекст

(edit) Теперь, когда эта уязвимость уже давно исправлена ​​и раскрыта, я могу добавить немного контекста.

Я задал этот вопрос в то время, когда я изучал CVE 2014-0475 . glibc имел ошибку в том, что он позволял пользователю использовать такие локали, как LC_ALL=../../../../tmp/evil-locale , которые разрешены относительно стандартного пути поиска в LC_ALL=../../../../tmp/evil-locale системе и, таким образом, позволяют использовать любой файл как определение локали.

Я мог бы создать локальный жулик, например, с одним байтом на символьную кодировку, где большинство символов, кроме s , h и некоторых других, считались пробелами, и это делало бы bash run sh при анализе типичного файла Debian /etc/bash.bashrc (и который может быть использован для получения доступа к оболочке на сервере хостинга git например, если bash используется в качестве оболочки входа пользователя git сервера и что ssh сервер принимает LC_* / LANG и что злоумышленник может загружать файлы на сервер) ,

Теперь, если бы я когда-либо обнаружил LC_CTYPE (скомпилированное определение локали) в /tmp/evil , как бы я узнал, что это был изгоев и каким образом.

Поэтому моя цель – не компилировать это определение локали, а если нет, то, по крайней мере, знать, какой символ (вместе с их кодировкой) находится в заданном классе символов.

Поэтому, имея в виду:

  • Решения, которые смотрят на исходные файлы для локали (определения локали, такие как в /usr/share/i18n/locale на Debian), бесполезны в моем случае.
  • Свойства символа Юникода не имеют значения. Я только забочусь о том, что говорит местность. В системе Debian, даже между двумя локалями системы UTF-8, не говоря уже об изгоев, список символов в классе может быть другим.
  • Такие инструменты, как recode , python или perl которые выполняют преобразование символов byte / multi-byte to / from, не могут использоваться так, как они могут (и на практике) сделать преобразование иным способом, чем языковой стандарт.

По крайней мере, по GNU, системам FreeBSD или Solaris этот подход с грубой силой работает:

 #include <wctype.h> #include <locale.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { unsigned long i; int need_init; wctype_t type; FILE* to_perl; setlocale(LC_ALL,""); if (argc != 2) { fprintf(stderr, "Usage: %s <type>\n", (argc?argv[0] : "???")); exit(1); } if (!(type = wctype(argv[1]))) { fprintf(stderr, "Invalid type: \"%s\"\n", argv[1]); exit(1); } need_init = wctomb(0, 0); to_perl = popen("perl -Mcharnames=full -ane '" "printf \"%17s U+%04X %s\n\", join(\" \", @F[1..$#F])," "$F[0], charnames::viacode($F[0])'", "w"); #ifdef SUPPORT_ROGUE_LOCALES for(i=0; i<=0x7fffffff; i++) { #else for(i=0; i<=0x10ffff; i++) { if (i == 0xd800) i = 0xe000; /* skip UTF-16 surrogates */ #endif if (iswctype(i, type)) { int n; unsigned char buf[1024]; if (need_init) wctomb(0, 0); n = wctomb(buf, i); if (n > 0) { int c; fprintf(to_perl, "%lu", i); for (c = 0; c < n; c++) fprintf(to_perl, " %02X", buf[c]); putc('\n', to_perl); } } } pclose(to_perl); return 0; } 

В то время как на C / POSIX wchar_t является непрозрачным типом, который не имеет отношения к Unicode и гарантирован только для покрытия всех символов, поддерживаемых локалью системы, на практике в большинстве систем, поддерживающих Unicode, значения соответствуют коду Unicode точки и определения локали сами основаны на Unicode.

Unicode должен быть надмножеством всех известных кодировок, поэтому циклирование всех допустимых кодовых точек в Unicode (от 0 до 0xD7FF и 0xE000 до 0x10FFFF) должно содержать список, по крайней мере, всех символов, поддерживаемых данной кодировкой.

Здесь мы используем стандартный API-интерфейс системы, чтобы проверить, какие из них относятся к данному типу, и преобразовать его в свою кодированную форму в кодировке локали. Мы используем perl и его модуль charnames только для получения имени из данного charnames Unicode.

В локалях, использующих кодировки с сохранением состояния, такие как ISO-2022-JP, мы гарантируем, что закодированная форма отображается из начального состояния по умолчанию.

Я не нашел систему, в которой были установлены локали с кодировкой с кодированием состояния, но, по крайней мере, в системах GNU, можно создать некоторые из них, чтобы можно было использовать локальную сеть (и, по крайней мере, инструменты GNU не работают должным образом в тех локали). Например, с пользовательским языковым стандартом, который использует ISO-2022-JP с обычным ja_JP , я получаю:

 $ LOCPATH=$PWD LC_ALL=ja_JP.ISO-2022-JP ~/list-type blank 09 U+0009 CHARACTER TABULATION 20 U+0020 SPACE 1B 24 42 21 21 U+3000 IDEOGRAPHIC SPACE 

Сравнить с:

 $ LC_ALL=ja_JP.eucjp ~/list-type blank 09 U+0009 CHARACTER TABULATION 20 U+0020 SPACE A1 A1 U+3000 IDEOGRAPHIC SPACE 

В ISO-2022-JP последовательность 1B 24 42 ( \e$B ) переключается с ASCII на состояние, в котором символы выражаются в виде 2 (7-битных) байтов (здесь 21 21 для этого ИДЕОГРАФИЧЕСКОГО ПРОСТРАНСТВА). В то время как в EUCJP это одни и те же байты, но переключение состояний выполняется путем переключения 8-го бита ( A1 = 21 | 0x80 ), что делает его более безстоящим.

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

Хотя для нормального языка символы не могут находиться за пределами 0..0xD7FF, 0xE000..0x10FFFF, для языковой системы изгоев любой символ в диапазоне, поддерживаемом wchar_t, может быть. Например, я мог бы создать локаль, где символы U + DCBA или U + 12345678 (или были бы символами, если бы они были разрешены) были пробелами . Вот почему вы хотите скомпилировать этот код с -D SUPPORT_ROGUE_LOCALES чтобы покрыть их, хотя это означает, что требуется много времени для сканирования всего списка.

Я не мог использовать решение @ mikeserv, поскольку recode использует свои собственные преобразования, больше не поддерживается и поддерживает только символы Unicode до 0xFFFF, а GNU tr по крайней мере не работает с многобайтовыми символами.

Я не мог использовать @ ChrisDown, поскольку python не имеет интерфейсов для классов символов POSIX.

Я пробовал Perl, но он подделка для кодовых точек между 128 и 255 для многобайтовых локалей, отличных от UTF-8, и не использует библиотеки преобразования системы.