Одного сонячного ранку я сидів перед ноутбуком і переробляв якийсь код на C#. Все йде добре, і це буде продуктивний день. Потім я додав забагато рівних знаків до літералу постійного рядка, і все вибухнуло. Продуктивність зникла. Спокійна реконструкція неділі позаду. Навіть сонце вирішило сховатися за хмарами.
Після 30-40 хвилин, намагаючись зрозуміти, що я роблю не так, я зрозумів, що це не я. Це Microsoft. Схоже, я натрапив на старий баг у функції декодування Base64.Convert.fromBase64StringЦей баг існує з моменту, коли у 2003 році був представлений .NET 1.1. Вау! Це старе. І це не дуже складно відтворити. Гарна робота:
Технічно, це незаконний Base64. Юридична версія — «abc=». Зверніть увагу, що лише один заповнювач =. Кодування Base64 представляє кожні 6 біт двійкового вхіду в одному ASCII-символі. Це означає, що кожні 4 символи в рядку, закодованому Base64, представляють 3 байти. Коли закодовані дані не є кратними 3 байтам, енкодер Base64 додає заповнюючий символ, щоб Base64 став кратним 4 символам. Це генерує рядок Base64, який правильно заповнений "abc=". Додавання ще одного = анулює його.
Base64 «abc=» декодується як два байти [105, 183]. Саме так. Додавання ще одного символу заповнення в кінці не повинно суттєво змінювати закодоване значення. Це як додати пробіл у кінці речення. Так, воно є, але це не змінює значення речення. Але .NET так не вважає. "abc==" декодується як байт [109]. Не лише він став коротшим, що було дивно, бо ми зробили введення довшим. Вона також стала іншою. Перший байт змінюється з 105 до 109. І це не створювало винятків. Додайте ще один = і отримаєте виняток. Дивовижні!
Код:
Вихід:
'abc=' -> [105, 183] 'abc==' -> [109] Дивовижно, що ніхто не помічав цього стільки років. Або його знайшли, але не виправили. Base64 дуже важливий для обміну інформацією в мережі. Його можна побачити всюди. Однак .NET вже багато років не позбувся несправного декодера Base64.
Спочатку я не міг у це повірити і почав розслідувати. Я довго гуглив і не знайшов багато. Потім я написав на StackOverflow і теж не мав особливого успіху. Коли я зрозумів, що відбувається, мені навіть довелося відповідати на власні запитання. Після деякого часу пошуку на GitHub я натрапив на виправлення, зроблене в .NET Core у липні 2018 року. Отже, остання версія .NET Core правильно вирішує цю проблему і видає виняток:
Необроблене виключення: System.FormatException: Вхідний запис не є дійсним рядком Base-64, оскільки містить символ без основи 64, більше двох символів заповнення або незаконний символ серед Заповнювачі персонажів. at System.Convert.FromBase64CharPtr(Char* inputPtr, Int32 inputLength) на System.Convert.FromBase64String(String s) на Program.DecodeAndPrint (String base64) у ./base64/Program.cs:line 13 на Program.Main() у ./base64/Program.cs:line 8 Їм знадобилося близько 15 років, щоб знайти і виправити проблему. Цікаво, що ніхто насправді не намагався це виправити спеціально. Це сталося, коли вони переписали код, щоб зробити його швидшим:
Convert.FromBase64() має тонку помилку, коли другий незаконно наповнений символ у кінці рядка змушує декодування «успішно» через видалення передостаннього символу.
Ми знаходимося на . Цю помилку було ненавмисно виправлено при оптимізації API у NetCore 2.1. Додайте тести до помилок журналу і переконайтеся, що ми не повертаємося назад. Цю проблему вирішили в .NET Core 2.2. Але в поточній останній версії .NET Framework 4.7.2 проблеми все ще виникають. Схоже, що він зламаний і в моно.
Обхідний шлях у .NET 4.7.2 — заповнити неправильно заповнений рядок чимось на кшталт:
Оригінальний:Вхід за гіперпосиланням видно.
|