LINUX.ORG.RU

Ищу набор тестов для проверки парсера UTF-8

 ,


2

3

Организовал я свой велопарсер текста c кодировкой UTF-8. Теперь я хочу его проверить. Существуют ли в природе «эталонные» строки, по которым можно протестировать парсер? В сети нашёл только это https://www.w3.org/2001/06/utf-8-wrong/UTF-8-test.html

★★
Ответ на: комментарий от firkax

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

u5er ★★
() автор топика
Ответ на: комментарий от firkax

У меня пока просто парсер. Он на вход получает обычную строку, а на выходе выдает номер символа из таблицы юникода. Разбор юникодных штук я вряд ли запилю, т.к. я слишком ленивый xD Я думаю, мне будет достаточно обычных одиночных символов.

u5er ★★
() автор топика
Ответ на: комментарий от firkax

Если его парсер это аналог mbrtowc(), только не для произвольной мультибайтовой кодировки текущей локали а только для utf-8, то там не полстраницы. Там много кейсов. Когда надо вернуть EILSEQ, когда 0, когда количество потреблённых байтов. Когда надо обновить mbstate, когда не надо, когда надо сбросить его в initial state.

iliyap ★★★★★
()
Ответ на: комментарий от iliyap

то там не полстраницы. Там много кейсов

Поскольку непонятно какой там интерфейс, оставим его в свободной форме. Примерно так:

parse_utf8(uchar const *src, size_t srcsz) {
  uchar c1, c2, c3, c4;
  uint32 uc;
  while(srcsz) {
    c1 = *src;
    if(c1<=0x7F) { printf("Symbol: %02X\n", c1); src++; srcsz--; continue; }
    if(c1<=0xBF || c1>0xF7) { printf("Invalid input: %02X\n", c1); src++; srcsz--; continue; }
    if(srcsz<2) { printf("Truncated input: %02X\n", c1); break; }
    if(((c2=src[1])&0xC0)!=0x80) { printf("Invalid input: %02X\n", c1); src++; srcsz--; continue; }
    if(c1<=0xDF) { uc = ((c1&0x1F)<<6) | (c2&0x3F); len = 2; }
    else {
      if(srcsz<3) { printf("Truncated input: %02X %02X\n", c1, c2); break; }
      if(((c3=src[2])&0xC0)!=0x80) { printf("Invalid input: %02X %02X\n", c1, c2); src+=2; srcsz-=2; continue; }
      if(c1<=0xEF) { uc = ((c1&0x0F)<<12) | ((c2&0x3F)<<6) | (c3&0x3F); len = 3; }
      else {
        if(srcsz<4) { printf("Truncated input: %02X %02X %02X\n", c1, c2, c3); break; }
        if(((c4=src[3])&0xC0)!=0x80) { printf("Invalid input: %02X %02X %02X\n", c1, c2, c3); src+=3; srcsz-=3; continue; }
        uc = ((((uint32)c1)&7)<<18) | ((((uint32)c2)&0x3F)<<12) | ((c3&0x3F)<<6) | (c4&0x3F); len = 4;
      }
    }
    if(uc<=0x7F || uc<=0x7FF && len>2 || uc<=0xFFFF && len>3 || uc>0x10FFFF || uc>=0xFDD0 && uc<=0xFDEF || (uc&0xFFFE)==0xFFFE || uc>=0xD800 && uc<=0xDFFF)
      printf("Malformed symbol: %08lX\n", (ulong)uc);
    else
      printf("Symbol: %08lX\n", (ulong)uc);
    src+=len; srcsz-=len;
  }
}

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 4)
Ответ на: комментарий от firkax

Без обид, но это нечитаемая мешанина, перегруженная условными операторами :) Ты сам разберёшься в этом коде через пару лет без поллитры?

Критикую-предлагаю-делаю:

#include <stdio.h>
#include <stdint.h>

struct utf8Decoder{
	const char*   text;
	unsigned char isValid;
	uint32_t      char32;
};

/**
  Функция декодирования символов из utf-8.
  
  text - входная строка с текстом в формате utf-8. Строка должна
  заканчиваться NULL-терминатором.
  
  isValid - флаг, указывающий на допустимость декодированной
  последовательности байт. Нулевое значение говорит о недопустимости
  последовательности, ненулевое - о допустимости. Недопустимый символ
  следует игнорировать и заменить на "битый" символ.
  
  char32 - декодированный символ.
  
  Функция возвращает количество реально декодированных байт - ширину 
  символа. В случае достижения конца строки, функция вернёт 0.
*/
int decodeUtf8( struct utf8Decoder* decoder ){
	
	unsigned char charWidth;
	
	decoder->isValid = 0xff;
	
	/*
	Байты, которые никогда не встречаются в UTF-8: 0xC0, 0xC1, 0xF5–0xFF
	Байт продолжения (0x80–0xBF) в качестве начала последовательности
	A non-continuation byte (or the string ending) before the end of a character
	An overlong encoding (0xE0 followed by less than 0xA0, or 0xF0 followed by less than 0x90)
	Слишком длинное кодирование (0xE0, за которым следует менее 0xA0, или 0xF0, за которым следует менее 0x90)
	Последовательность из 4 байтов, которая декодируется в значение больше, чем U+10FFFF (0xF4, за которым следует 0x90 или больше)
	
	Декодер не должен потреблять допустимые последующие байты, если
	встречается недопустимая последовательность.
	
	Заменяющий символ � находится на позиции U+FFFD
	
	1-byte  0x000000 - 0x00007f
	2-byte  0x000080 - 0x0007ff
	3-byte  0x000800 - 0x00ffff
	4-byte  0x010000 - 0x10ffff
	*/
	
	//TODO: орагнизовать поддержку 5 и 6 байта из вредности! =P
	
	/// первый байт
	
	//проверяем нуль-терминатор
	if( !decoder->text[0] ) return 0;
	
	//по маске определяем количество байт
	
	//1 байт
	if( !(decoder->text[0] & 0x80) ){
		//Дополнительные проверки не нужны. Если старший бит 0, то это 
		//может быть только однобайтовый символ, который может принимать
		//значения от 0x01 до 0x7F. Проверка на 0x00 была выше.
		decoder->char32 = decoder->text[0];
		return 1;
	}
	
	//2 байта
	if( (decoder->text[0] & 0xE0) == 0xC0 ){
		decoder->char32 = ( decoder->text[0] & 0x1F ) << 6;
		charWidth = 2;
	}
	
	//3 байта
	else if( (decoder->text[0] & 0xF0) == 0xE0 ){
		decoder->char32 = ( decoder->text[0] & 0x0F ) << ( 6 * 2 );
		charWidth = 3;
	}
	
	//4 байта
	else if( (decoder->text[0] & 0xF8) == 0xF0 ){
		decoder->char32 = ( decoder->text[0] & 0x07 ) << ( 6 * 3 );
		charWidth = 4;
	}
	
	//битьё
	else{
		decoder->isValid = 0x00;
		return 1;
	}
	
	/// второй байт
	
	//проверяем заголовок
	if( (decoder->text[1] & 0xC0) != 0x80 ){
		decoder->isValid = 0x00;
		return 1;
	}
	
	//делаем выборку данных
	decoder->char32 |= ( decoder->text[1] & 0x3F ) << ( 6 * (charWidth - 2) );
	
	if( charWidth == 2 ){
		
		//проверка допустимости для ширины 2 байта 0x000080 - 0x0007ff
		if( decoder->char32 <   0x80 ) decoder->isValid = 0x00; 
		if( decoder->char32 > 0x07FF ) decoder->isValid = 0x00;
		return 2;
	}
	
	
	/// третий байт
	
	//проверяем заголовок
	if( (decoder->text[2] & 0xC0) != 0x80 ){
		decoder->isValid = 0x00;
		return 2;
	}
	
	//делаем выборку данных
	decoder->char32 |= ( decoder->text[2] & 0x3F ) << ( 6 * (charWidth - 3) );
	
	if( charWidth == 3 ){
		
		//проверка допустимости для ширины 3 байта 0x000800 - 0x00ffff
		if( decoder->char32 < 0x0800 ) decoder->isValid = 0x00;
		if( decoder->char32 > 0xFFFF ) decoder->isValid = 0x00;
		return 3;
	}
	
	/// четвёртый байт
	
	//проверяем заголовок
	if( (decoder->text[3] & 0xC0) != 0x80 ){
		decoder->isValid = 0x00;
		return 3;
	}
	
	//делаем выборку данных
	decoder->char32 |= ( decoder->text[3] & 0x3F ) << ( 6 *(charWidth - 4) );
	
	//проверка допустимости для ширины 4 байта 0x010000 - 0x10ffff
	if( decoder->char32 < 0x010000 ) decoder->isValid = 0x00;
	if( decoder->char32 > 0x10FFFF ) decoder->isValid = 0x00;
	return 4;
}
u5er ★★
() автор топика
Ответ на: комментарий от u5er

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

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

Единственное ты не учёл:

non-characters (FDD0..FDEF FFFE, FFFF, xxFFFE, xxFFFF)

surrogate characters (D800 .. DFFF)

private-use (E000..F8FF F0000..FFFFD 100000..10FFFD)

Третье в целом не запрещено, но иногда оно может не допускаться. Да вобщем-то и первые два никто не может запретить парсить, это просто очередные дурацкие правила юникода. Можно и вообще убрать все проверки, разрешив в том числе 5-6 байтовые последовательности.

А также надо учесть, что в джаве например ноль кодируется как C0 80 - это позволяет хранить и нулевой символ, и отдельно использовать настоящий ноль (00) как маркер конца строки. Поэтому правило про неиспользование не той длины тоже не строгое по факту.

firkax ★★★★★
()
Ответ на: комментарий от firkax

в джаве например ноль кодируется как C0 80

А вот это довольно интересно. В RFC прямо сказано, что

The octet values C0, C1, F5 to FF never appear.

Получается, что и эти правила тоже косвенные?

u5er ★★
() автор топика