Декодирование цитируемая для печати строка включает в себя три вещи:
Игнорировать мягкие переводы строк. Это =
, за которыми следует символ новой строки.
Преобразование =
, за которым следуют две шестнадцатеричные цифры, в символ, код которого соответствует шестнадцатеричному значению
Существует три основных подхода к декодированию данных:
Входной фильтр. Вместо, например fgetc()
, вы используете функцию, которая читает и декодирует вводимый в кавычки ввод.
Преобразование в новый буфер. См. convert()
функцию в Крейг Эстейс ответ на этот же вопрос.
Преобразование на месте. Это возможно, потому что каждая действительная строка в кавычках, пригодная для печати, имеет длину не менее той же самой декодированной.
Входной фильтр. Для простоты, давайте посмотрим на один символ за раз. (Обратите внимание, что многие символы UTF-8 длиннее, чем один символ.)
Во-первых, нам нужна вспомогательная функция для преобразования шестнадцатеричных цифровых символов в их соответствующие арифметические значения:
static inline int hex_digit(const int c)
{
switch (c) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'A': case 'a': return 10;
case 'B': case 'b': return 11;
case 'C': case 'c': return 12;
case 'D': case 'd': return 13;
case 'E': case 'e': return 14;
case 'F': case 'f': return 15;
default: return -1;
}
}
В большинстве случаев вы также можете написать это как
static inline int hex_digit(const int c)
{
if (c >= '0' && c <= '9')
return c - '0';
else
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
else
if (c >= 'a' && c <= 'F')
return c - 'a' + 10;
else
return -1;
}
или даже как
static signed char hex_digit_value[UCHAR_MAX + 1];
static inline int hex_digit(const int c)
{
return hex_digit_value[(unsigned char)c];
}
static inline void init_hex_digit_values(void)
{
int i;
for (i = 0; i <= UCHAR_MAX; i++)
hex_digit_value[i] = -1;
hex_digit_value['0'] = 0;
hex_digit_value['1'] = 1;
hex_digit_value['2'] = 2;
hex_digit_value['3'] = 3;
hex_digit_value['4'] = 4;
hex_digit_value['5'] = 5;
hex_digit_value['6'] = 6;
hex_digit_value['7'] = 7;
hex_digit_value['8'] = 8;
hex_digit_value['9'] = 9;
hex_digit_value['A'] = hex_digit_value['a'] = 10;
hex_digit_value['B'] = hex_digit_value['b'] = 11;
hex_digit_value['C'] = hex_digit_value['c'] = 12;
hex_digit_value['D'] = hex_digit_value['d'] = 13;
hex_digit_value['E'] = hex_digit_value['e'] = 14;
hex_digit_value['F'] = hex_digit_value['f'] = 15;
}
, где init_hex_digit_values()
вызывается один раз в начале программы. Я предпочитаю первую форму, так как она самая переносимая, но вторая форма - это то, что вы обычно видите.
Третья форма, использующая массив hex_digit_value[]
, является примером преждевременной оптимизации. В некоторых случаях он может быть немного быстрее, чем другие (но различия определенно слишком малы, чтобы иметь значение на практике), но это может быть полезно, если нужно поддерживать сильно различающиеся однобайтовые наборы символов (например, EBDIC и ASCII) используя тот же код.
Во-первых, чтение декодированного символа из потока (файла или дескриптора), который содержит данные для печати в кавычках:
int get_quoted_printable_char(FILE *from)
{
int c, c2, hi, lo;
/* Paranoid check. */
if (!from || ferror(from) || feof(from))
return EOF;
while (1) {
c = fgetc(from);
if (c != '=')
return c;
/* Soft newline? */
c = fgetc(from);
if (c == '\n')
continue;
/* '=' at the end of input? */
if (c == EOF)
return EOF;
hi = hex_digit(c);
if (hi < 0) {
/* Invalid input; emit '=' instead. */
ungetc(c, from);
return '=';
}
c2 = fgetc(from);
if (c2 == EOF) {
/* Invalid input; emit '=' <c> instead. */
ungetc(c, from);
return '=';
}
low = hex_digit(c2);
if (lo < 0) {
/* Invalid input; try to emit '=' <c> <c2> instead. */
ungetc(c2, from);
ungetc(c, from);
return '=';
}
return low + 16 * high;
}
}
Цикл существует в случае, если на входе имеется более одной последовательной мягкой новой строки. Такого не должно быть на самом деле, но если это произошло, мы хотим игнорировать их все.
Если вы хотите скопировать цитируемый поток для печати в файл, вам понадобится только вышеперечисленное и, например,
int save(FILE *source, const char *filename)
{
FILE *target;
int c;
if (!source || ferror(source))
return -1; /* Invalid source handle */
if (!filename || !*filename)
return -2; /* Invalid filename */
target = fopen(filename, "w");
if (!target)
return -3; /* Cannot open filename for writing */
while (1) {
c = get_quoted_printable_char(source);
if (c == EOF)
break;
if (fputc(c, target) == EOF)
break;
}
if (!feof(source) || ferror(source)) {
fclose(target);
remove(filename);
return -4; /* Error reading source. */
}
if (fclose(source)) {
fclose(target);
remove(filename);
return -4; /* Error closing source (delayed read error). */
}
if (ferror(target) || fflush(target)) {
fclose(target);
remove(filename);
return -5; /* Write error */
}
if (fclose(target)) {
remove(filename);
return -5; /* Error closing target; delayed write error */
}
/* Success. */
return 0;
}
, который особенно осторожен против ошибок чтения и записи. Он не слишком быстрый, потому что он использует библиотеку C для буферизации ввода, но он также не слишком медленный. Тот факт, что он не использует какие-либо явные буферы (полагаясь на стандартную библиотеку C, чтобы решить, как буферизовать исходный код и записываемый файл), в целом делает его вполне приемлемым.
Преобразование в новый буфер или на место довольно похоже:
size_t decode_quoted_printable(char *dst, const char *src)
{
const char *const origin = dst;
/* Neither pointer may be NULL. src == dst is okay, however. */
if (!dst || !src) {
errno = EINVAL;
return 0;
}
/* Copy loop. */
while (*src)
if (*src == '=') {
if (src[1] == '\0') {
/* '=' at the end of string. Skipped. */
break;
} else
if (src[1] == '\n') {
/* Soft newline. Skip both =\n and =\n\r newlines. */
if (src[2] == '\r')
src += 3;
else
src += 2;
} else
if (src[1] == '\r') {
/* Soft newline. Skip both =\r and =\r\n newlines. */
if (src[2] == '\n')
src += 3;
else
src += 2;
} else {
const int hi = hex_digit((unsigned char)(src[1]));
const int lo = hex_digit((unsigned char)(src[2]));
if (hi >= 0 && lo >= 0) {
*(dst++) = lo + 16*hi;
src += 3;
} else {
/* Error in input format. We are permissive,
and reproduce the erroneous `=XY` as-is. */
*(dst++) = *(src++);
}
}
} else
if (*src == '\n') {
if (src[1] == '\r')
src += 2;
else
src += 1;
*(dst++) = '\n';
} else
if (*src == '\r') {
if (src[1] == '\n')
src += 2;
else
src += 1;
*(dst++) = '\n';
} else
*(dst++) = *(src++);
/* Terminate result to make it a string. */
*dst = '\0';
/* Just in case the source was an empty string, we clear
errno to zero. This also means we always set errno,
which is a bit rare, but makes the use of this function
easy: errno is nonzero iff there was an error. */
errno = 0;
return (size_t)(dst - origin);
}
Обратите внимание, что поскольку строковые литералы не могут быть изменены, вы не можете сделать char *data = "foo"; decode_quoted_printable(foo, foo);
.
Однако вы можете сделать char data[] = "foo"; decode_quoted_printable(foo, foo);
, потому что он объявляет массив символов, который просто инициализируется строкой "foo"
.
Обратите внимание, что вышеуказанная функция также выполняет автоматическое универсальное преобразование новой строки. Таким образом, он поддерживает все четыре соглашения о новой строке \r\n
, \n\r
, \r
и \n
и преобразует их все в стандартные символы новой строки C \n
.
Целевой буфер должен быть по крайней мере таким же, как исходный буфер, и вы можете использовать тот же целевой буфер, что и исходный, если он изменчив (не является литеральной строкой или указывает на литеральную строку).
Отличие от get-one-decoded-symbol от потокового подхода состоит в том, что последний требует, чтобы весь контент находился в буфере в памяти. Является ли это плюсом или минусом, зависит от контекста.