RFC 9839 ve Sorunlu Unicode
(tbray.org)- RFC 9839, yazılım geliştirme sırasında metin alanlarında yer alabilecek sorunlu Unicode karakterlerini açık biçimde tanımlar
- Bu RFC, farklı diller ve kütüphaneler arasında bu karakterlerin işlenmesindeki tutarsızlıktan doğan sorunları ele alır
- 9839, daha az sorunlu olan üç alt küme önerir ve bunların isteğe bağlı olarak kullanılabileceğini belirtir
- Mevcut PRECIS framework ile karşılaştırıldığında uygulanması daha kolay ve daha basittir
- RFC 9839 için bir Go dili kütüphanesi de birlikte yayımlandı ve pratik kullanımda yardımcı oluyor
Arka plan ve RFC 9839'a genel bakış
- Unicode, neredeyse tüm metin verisi işlemede standart olarak kullanılır
- Ancak gerçek veri yapıları veya protokol tasarımı sırasında tüm Unicode karakterlerine izin vermek sorun yaratabilir
- Paul Hoffman ve yazar, tekrar eden Unicode sorunları için net ölçütler sunmak amacıyla IETF'ye bireysel taslak sundu
- İki yıllık tartışmanın ardından resmî standart olarak kabul edildi ve RFC 9839 olarak yayımlandı
- Bu belge, sorunlu karakter türlerini, neden sorunlu olduklarını (teknik ve standart gerekçeleriyle) ve kullanıcıların seçip kullanabileceği üç alt kümeyi ayrıntılı biçimde açıklar
RFC 9839'un ana içeriği
- Yazılım ve ağ ortamlarında metin alanı tasarlanırken mutlaka başvurulması gereken bir belgedir
- RFC 9839, 10 sayfa uzunluğundadır ve IETF standart belgeleri arasında kısa sayılır
- Temelde yazılım geliştiriciler ve ağ mühendisleri için kolay anlaşılır biçimde yazılmıştır
Sorunlu Unicode karakterlerine örnekler
- Örnek olarak JSON içindeki
usernamealanına aşağıdaki gibi bir dize gelebilir{ "username": "\u0000\u0089\uDEAD\uD9BF\uDFFF" } - Her kod noktasının taşıdığı sorunlar
U+0000: Anlamsız bir NULL karakteridir ve bazı programlama dillerinin davranışını bozabilirU+0089: C1 kontrol kodu (CHARACTER TABULATION WITH JUSTIFICATION) olup davranışı karmaşık ve tutarsızdırU+DEAD: Eşleşmemiş surrogate karakterdir; UTF-16'nın sınırlarından kaynaklanan bir sorundur. İdeal olmayan veri oluşmasına yol açar\uD9BF\uDFFF(gerçekteU+7FFFF) : Noncharacter olup standart gereği değiş tokuş edilmesi yasaktır
- Bu tür kod noktaları, veri yapıları ve protokoller içinde tutarlı biçimde işlenememeye ve beklenmeyen hatalara yol açar
- RFC 9839, bu sorunlu karakterleri resmî olarak tanımlar ve hariç tutulması gereken türleri açıkça belirtir
JSON'un tasarımı ve sınırları
- Bu durum, JSON'un yaratıcısı Doug Crockford'un sorumluluğu değildir
- JSON, Unicode'un yeterince olgunlaşmadığı bir dönemde tasarlandığı için karakter kümesini sıkı biçimde sınırlayamadı
- Artık standardı değiştirmek mümkün olmadığından, sorunlu karakterleri deneyimsel olarak dışlama yaklaşımı gerekir
IETF'nin PRECIS framework'ü ile farkı
- 2025 tarihli RFC 9839'dan önce de IETF, RFC 8264 (PRECIS Framework) gibi çeşitli standartlar sunuyordu
- Bu framework, uluslararasılaştırılmış dizelerin arındırılması, uygulanması ve karşılaştırılması yöntemlerini ayrıntılı biçimde ele alır
- 43 sayfa uzunluğundadır ve arka plan açıklamaları ile çözüm yaklaşımı kapsamlıdır
- PRECIS, Unicode sürümlerine güçlü biçimde bağımlıdır; karmaşıktır ve uygulanması zordur
- RFC 9839, kısa ve pratiktir; yeni protokoller tanımlanırken hızlı benimsenmesi daha kolaydır
RFC 9839'un alt kümeleri ve kullanım örnekleri
- 9839, üç gerçekçi alt küme sunar: scalars, XML, assignables
- Her alt küme, hariç tutulacak sorunlu karakter aralığını biraz farklı belirler
- Aşağıda, başlıca veri formatlarının ve RFC 9839 alt kümelerinin sorunlu karakterleri nasıl ele aldığına dair tablonun özeti yer alır
- CBOR, TOML, XML, YAML gibi bazı formatlar surrogate veya kontrol karakterlerini kısmen dışlar
- I-JSON, surrogate ve noncharacter karakterleri dışlar
- Genel JSON, Protobufs bunları dışlamaz
- XML, YAML, charset özellikleri nedeniyle noncharacter/kontrol kodlarının yalnızca bir kısmını hariç tutar
- Not: XML ve YAML, Basic Multilingual Plane dışındaki noncharacter karakterleri hariç tutmaz
Go dili için RFC 9839 kütüphanesi
- RFC 9839'un üç alt kümesi için karakter doğrulamasını destekleyen küçük bir Go kütüphanesi yayımlandı
- Yeterince test edildi; optimizasyon çalışmaları ise hâlâ sürüyor
- Gerçek üretim ortamlarında test ve geri bildirim memnuniyetle karşılanıyor
RFC 9839'un önemi ve hazırlık süreci
- RFC 9839, ortak yazarlar ve çok sayıda geri bildirim turunun ardından, 15'ten fazla taslak revizyonundan sonra resmen yayımlandı
- Topluluktaki birçok uzmanın tartışma ve katkıları sayesinde ilk taslaktan çok daha olgun bir belgeye dönüştü
- Katkıda bulunanlar “Acknowledgements” bölümünde belirtiliyor
RFC bireysel gönderim deneyimi
- RFC 9839, bireysel gönderim (individual submission) olarak ilerletildi
- Working Group üzerinden yürüyen geleneksel yönteme göre emek ve süreç yükü daha fazladır
- Working Group katılım deneyimiyle karşılaştırıldığında, geleneksel yöntem daha verimli ve daha fazla tavsiye edilir
1 yorum
Hacker News görüşü
Bazı karakterlerin sorun çıkardığının açık olduğunu düşünüyorum, ancak veri yapısı ya da protokol tasarlayanların her türlü karaktere keyfi olarak izin vermemeye eğilimli olması en kötü senaryo gibi geliyor; buna uygun biçimde escape edilmiş olanlar bile dahil. Örneğin, kullanıcı adı doğrulamasının başka bir katmanda ele alınması gerektiğini düşünüyorum. Kullanıcı adının 60 karakterden kısa olması, emoji ya da zalgo karakterleri içermemesi, null byte içermemesi gibi kontroller yapılır ve API uygun bir hata döndürür. JSON parse aşamasında ön doğrulama yerine bu tür meseleler yüzünden başarısız olmasını istemem. Elbette kullanıcı adında belirli karakter sınıfları açıkça uygunsuzdur. Ama sekme karakteri vb. gerçekten kullanılan metin dosyaları gönderiyorsam, dilimin utf8
stringtipinin işleyebildiği şeylerin encode edilebilir olmasını beklerim. Özellikle null byte'ın birçok kullanım alanı var ve JSON içinde de gerçekten sık sık görülüyor. Ama sınırlı bir "normal" Unicode kümesi kullanılacaksa, standardın var olması herkesin kendi mini standardını yapmasından daha iyidir. Sonuç olarak fikir bizzat iyi görünüyor ama blog yazısındaki gerekçeler çok ikna edici gelmiyor2025 itibarıyla, düşük seviyeli wire protokollerinde kullanılacak string gösterimi için pratikte savunulabilir olanların yalnızca şunlar olduğunu düşünüyorum
Unicode Scalars(iyi biçimlenmiş UTF-16, Python string tipi)potansiyel olarak geçersiz UTF-16(WTF-8, JavaScript string tipi)potansiyel olarak geçersiz UTF-8(byte array, Go string tipi)U+0000 yokseçeneğinin eklenmesi (buffer overflow zafiyetlerinden önce tasarlanmış dil/kütüphanelerle birlikte çalışmak için)Açık konuşayım, düz metin dosyalarında C0 (satır sonu ve istemeyerek de olsa HT hariç) ile C1 karakterlerinin kullanılmamasını isterim. ANSI renk işaretlemesi gibi şeyleri saklamak istemeyi anlıyorum, ama bu durumda o veri aslında düz metin değil, bir tür metin işaretleme formatıdır. Markdown'a benzer, sadece C0 aralığında encoding kullanır. Verinin
catkomutu vb. ile güzel görünmesi, onun düz metin olduğu anlamına gelmez. Düz metinle encode edilmiş işaretleme formatlarının birlikte çalışabilirlik nedeniyle yaygın olduğunu da kabul ediyorumVeri yapılarında ve protokollerde keyfi karakter kümelerini yasaklamaya başlamanın en kötü şey olduğu görüşünün bizzat kendisinin gerçeklikten kopuk bir düşünce olduğunu düşünüyorum. Asıl en kötü şey, parser vb. yazılım hataları nedeniyle güvenlik ihlalinin yaşanmasıdır
Kullanıcı adlarında UTF-8'e izin veren sistemler var mı merak ediyorum. Programatik olarak işlenen ya da değerlendirilen tüm tanımlayıcıların (giriş kullanıcı adı, parola vb.) mutlaka ASCII olması gerektiği bana çok açık geliyor. ISO-8859-1 bile değil, sadece ASCII kullanılmalı. Unicode bu tür kullanım için uygun değil. Kullanıcı adını ekranda göstermek gibi durumlarda sorun olmayabilir, ama sistem girişi için tanımlayıcı olarak ASCII dışı encoding kesinlikle yasaklanmalı. Klavye yazılımı bile ASCII dışına çıkıldığında görsel temsil açısından UTF-8'in tutarlılığını kendi başına garanti edemez; işletim sistemi ve ayarlara göre daha da karışır. Gelecekte kalacak binary'lerle Unicode'u yorumlayan yapay zekanın aynı sonuca varacağının da garantisi yok. Ayrıca tutarlılık açısından, IVS durumlarının ya da normalizasyonun (NFC/NFD/NFKC/NFKD) kapsam içinde mi dışında mı olduğu ne RFC 9839'da ne de makalede açık. Amaç bölümü sanki tamamen eksik. Kabaca sadece "karakter olmayan kod noktaları" varmış gibi muğlak bir ifade bulunuyor
Kullanıcı adlarında neden emoji yasaklanmalı, merak ediyorum
IETF'nin 2025'e kadar Bad Unicode desteğini beklemediğini belirtmek isterim. Zaten çok daha önce RFC 8264: PRECIS Framework içinde çeşitli Bad Unicode sorunları geniş kapsamlı biçimde ele alındı. RFC 8265(bağlantı), 8266(bağlantı) gibi ilgili RFC'lere de bakmak faydalı olur. Genel olarak, metin yönünü değiştiren ya da giriş cihazına göre farklı encode edilebilen parolalar gibi şeyler kullanıcı adı/parolalarda kullanılmamalı. Bu RFC profilleriyle bunlar güvenli biçimde ele alınabilir. Bu tür amaçlarda
failing closed(daha katı biçimde engellemek) daha güvenlidir. Yeni emojiler çıktığında bile, kullanıcı adlarında izin verip her sayfayı etkilemesindense yasaklamayı ve muhafazakâr davranmayı tercih ederimUnicode'un açıkça "iyi" yanları var, ama istisna olarak hariç tutulması gereken karakterler olduğunu bilmek zorunda olmak hayal kırıklığı yaratıyor. Dilleri yazıya dökme biçimlerini kapsayıcı şekilde kabul etmeye çalışırken fazlasıyla karmaşık hale gelmiş. Hangi karakterlerin özel muamele görmesi gerektiğini sürekli düşünmek yorucu. Bu yüzden Unicode string'leri kendine özgü bir veri birimi olarak ele alıyorum. Girdi alırım, saklarım, render ederim, veri eşitliğini karşılaştırırım ama içeriği yorumlamaya çalışmam. Hatta string'leri birleştirmek ya da başka şekillerde işlemek bile tedirgin ediyor
Unicode, bitmek bilmeyen trivia'nın ve kötü kararların uçsuz bucaksız bir çukuru gibi. Örneğin ilgili RFC'lerde eski ASCII kontrol karakterleri için (görsel karışıklık riski nedeniyle) uyarılar var, ama Explicit Directional Overrides gibi ölümcül güvenlik sorunları barındıran yön değiştirme karakterlerinden hiç söz edilmiyor
Basit bir örnek olarak, birinci string yetim bir emoji değiştiriciyle bitip ikinci string değiştirilebilir bir emojiyle başlıyorsa, daha en baştan sorun çıkıyor. Daha karmaşık durumlar arttıkça sorun da büyüyor
Karmaşıklık büyük, ama bunların içindeki surrogate'lar ve kontrol kodları, dilleri yazıya dökmek için değil, geçmişten kalan tuhaf tasarım kararlarının sonucu olarak var
Unicode rahatsız edici olabilir, ama önceki diğer encoding standartlarından daha az rahatsız edici olduğunu düşünüyorum
Bence sorunların çoğu, geçersiz UTF-8 byte dizilerini reddederek ya da genel olarak hata döndürerek çözülebilir. Örneğin surrogate'lar zaten UTF-8'de yasadışıdır; dolayısıyla utf-8 kullanan bir dilde bu diziler için hata dönülmelidir. Gerçek sorun oluşturan şeyin, "kod noktası" düzeyinde problemli olanlar (ör. yazdırılamayanlar vb.) olduğunu düşünüyorum. Bunu yasadışı byte dizilerinden açıkça ayrı bir kavram olarak ele almak daha faydalıdır
Unicode zaten her kod noktasının kategorisini (General Category) tanımlayarak tuhaf karakter türlerini sınıflandırıyor. İlgili Wikipedia maddesine bakılabilir. Örneğin Python'da
unicodedata.category(chr(0))"Cc"(control),unicodedata.category(chr(0xdead))ise"Cs"(surrogate) döndürürTüm
legacy controlkarakterlerini, sadece literal olarak değil escape edilmiş string'lerde de (ör."\\u0027") dışlamak bana aşırı geliyor. C1 pek kullanılmıyor, o yüzden sorun değil; ama bazı C0 karakterlerinin gerçek kullanım örnekleri var. Escape, EOF, NUL vb. hâlâ açık kullanım alanlarına sahip diye düşünüyorumBiraz sıra dışı C0 karakterlerinin (
U+001E Record Separatorgibi) veri akışlarında faydalı olduğunu düşünüyorum. Belgelerde engellense bile akış verisinde yararlı olabilirProgram kaynak kodunda form feed (
U+000C) karakteri kullanıldığını gördüm. Emacs bunu sayfa düzeyinde gezinme için uzun zamandır destekliyor, bu yüzden bazen böyle şeyler yer alıyorUnicode'un iyi olduğunu düşünmüyorum. Karakter kümesi ne olursa olsun, fiilen hangi karakter türlerinin kullanılacağına (kontrol karakterleri, grafik karakterler, azami uzunluk vb.) sonuçta her uygulama kendi ihtiyaçlarına göre karar vermeli. JSON vb. içinde bir şeyleri dahil etmeye ya da hariç tutmaya çalışmanın çok etkisi yok. Unicode olsun, ASCII olsun ya da başka bir karakter kümesi olsun, belirli bir alt kümeye (veya üst kümeye) ad vermek bazen faydalı olabilir, ama bunun herkes için iyi bir seçim olduğunu sanmamak gerek. RFC 9839 bazı Unicode alt kümelerine isim veriyor, ama bunun benim kuracağım hizmet için otomatik olarak doğru olduğu anlamına gelmez. Benim vardığım sonuç, Unicode'u hiç kullanmama ya da zorunlu kılmama seçeneğini de düşünmek gerektiği
Girdiyi kontrol altına mı almalıyım, yoksa güvenilmeyen girdiyi güvenli biçimde çıktılayan bir veri tipine mi sarmalıyım (web+log+debug için), bunu düşünüyorum
Tek bir grafik birimine sığabilecek Unicode skaler değeri sayısı için standartta bir sınır olmasını isterdim. Son baktığımda (birkaç yıl önceydi) standartta böyle bir sınır yoktu; bunun yerine streaming uygulamalarda bir grafik biriminin 128 byte ile sınırlandırılması tavsiye ediliyordu. Bu tür bir sınırın standartta açıkça konması, bence uygulamayı çok daha kolaylaştırır ve gereksiz kısıtlar da getirmez
Gerçekten de "kontrol karakteri yoktur" varsayımıyla program yazıp bunun bozulduğu durumlar yaşadım (form feed sayfa ayırma için, escape karakteri terminal amaçlı vb. sık kullanılıyor). "Her şey UTF-8'dir" varsayımı da bozulabiliyor (eski veri dosyaları, log'lar vb.). Metin üzerinde anlamlı bir işlem yapmıyorsanız, içeriği değiştirmeden onu sadece bir byte dizisi olarak iletmek en iyisidir. Ama Microsoft Windows yüzünden bazen
char16_tdizileri geçirmek zorunda kalıyorsunuz. UTF-16'nın G/Ç davranışı UTF-8'den temelden farklı. Dönüştürme yaparken, dış veri → iç biçim dönüşümünde sırasıyla WTF-8 (UTF-16 için) ve surrogate escape (UTF-8 için) kullanılmalı. Bu iki yaklaşım karıştırılamaz