Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 Контент вільний від AI. Цей пост на 100% написаний людиною, як і все на моєму блозі. Насолоджуйтесь!

17.03.2023

Що робити з invalid byte sequence in UTF-8

Сучасним програмістам не доводиться багато думати про кодування рядків. Так склалось, що UTF-8 переміг, та ми можемо вважати, що всі рядки та тексти будуть в UTF-8, та майже ніколи не матимемо проблем. Як приклад: всякий файл формату JSON за стандартом має бути в кодуванні UTF-8. І це чудово, бо на початку моєї карʼєри реально треба було обирати кодування — для кирилиці одне з двох чи трьох; для латиниці інше; та всі вміли перекодувати текст як треба.

UTF-8 класне кодування; для латиниці воно дуже компактне, але при цьому дозволяє використати більш мільйона різних символів, що легко покриває всі мови світу, а також емоджі, системні символи та все інше. Але недоліки UTF-8 - це змінна довжина символу та те, що не кожна послідовність байтів є дійсним рядком UTF-8. Перше заважає, коли треба порахувати “наочну” довжину рядка, або розбити його за символами. А про друге доводиться деколи згадувати. Класична дірява абстракція!

Якби будь-який рядок був дійсним UTF-8, то можна було б хоча б ігнорувати кодування та обробляти всі рядки в UTF-8. Для випадків, де не треба інтерпретувати зміст рядка, цього цілком достатньо — ну є незрозумілі символи та і є. Але ж ні — деякі рядки мають недійсні послідовності символів, та їх не можна інтерпретувати як рядки UTF-8 в принципі, принаймні, не без втрати цих недійсних послідовностей. Тому стандартні бібліотеки, коли натраплять на недійсний символ, не проковтнуть його, а викличуть помилку — в Ruby це класично nvalid byte sequence in UTF-8.

Ми стикнулись з цією проблемою при обробці DNS записів; вони не мають визначеного за стандартом кодування. Ясно, що в більшості випадків DNS містить рядки ASCII, який фактично є підмножиною UTF-8, тому ми їх обробляли як UTF-8 і все було добре. Проте знаходяться записи, що викликають помилку кодування. Тому доводиться всі рядки приводити до UTF-8 перед обробкою. В Ruby для цього є метод encode: string.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: '�'). Тільки так можна “вичистити” рядок від недійсних символів. Є ще метод force_encoding, але він нам не підходить — бо він не перетворює рядок, а тільки встановлює кодування. force_encoding корисний, коли відомо, що рядок не в UTF-8, а в іншому кодуванні.