Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby의 유니코드 정규화

나는 최근에 특정 유니코드 문자를 사용하여 Ruby의 문자열 메서드 대부분을 테스트하여 예기치 않게 작동하는지 확인하는 기사를 게시했습니다. 많은 사람들이 그랬습니다.

몇몇 사람들이 이 기사에 대해 가지고 있는 한 가지 비판은 내가 테스트를 위해 정규화되지 않은 문자열을 사용하고 있다는 것이었습니다. 솔직히 유니코드 정규화에 대해 약간 모호했습니다. 많은 Rubyists가 있는 것 같습니다.

정규화를 사용하면 내 테스트에서 예기치 않게 동작하는 많은 유니코드 문자열을 가져 와서 Ruby의 문자열 메서드와 잘 작동하는 문자열로 변환할 수 있습니다. 그러나:

  1. 전환이 항상 완벽한 것은 아닙니다. 일부 유니코드 시퀀스는 항상 Ruby의 문자열 메서드가 오작동하도록 합니다.
  2. 수동으로 수행해야 하는 작업입니다. Ruby, Rails, DB 모두 기본적으로 자동으로 정규화되지 않습니다.

이 기사는 Ruby의 유니코드 정규화에 대한 간략한 소개입니다. 자신의 탐험을 위한 출발점이 되기를 바랍니다.

문자열을 정규화하자

<블록 인용>

String#unicode_normalize 메소드는 Ruby 2.2에서 도입되었습니다. Ruby로 작성되었기 때문에 C를 활용하는 utf8_proc 및 unicode gem과 같은 정규화 라이브러리만큼 빠르지 않습니다.

정규화가 필요한 이유는 유니코드에는 문자를 작성하는 방법이 두 가지 이상 있기 때문입니다. 문자 "Å" 코드 포인트 "\u00c5"로 나타낼 수 있습니다. 또는 문자 "A"와 악센트의 구성:"A\u030A" .

Ruby의 유니코드 정규화

정규화는 한 형식을 다른 형식으로 변환합니다.

"A\u030A".unicode_normalize        #=> 'Å' (same as "\u00C5")

물론 유니코드를 정규화하는 방법이 한 가지만 있는 것은 아닙니다. 너무 간단할 것입니다! "정규화 형식"이라고 하는 정규화 방법에는 네 가지가 있습니다. NFD, NFC, NFKD 및 NFKC와 같은 복잡한 약어를 사용하여 이름이 지정되었습니다.

String#unicode_normalize 기본적으로 NFC를 사용하지만 다음과 같이 다른 형식을 사용하도록 지정할 수 있습니다.

"a\u0300".unicode_normalize(:nfkc)       #=> 'à' (same as "\u00E0")

그러나 이것이 실제로 무엇을 의미합니까? 네 가지 정규화 형식은 실제로 무엇을 합니까? 살펴보겠습니다.

정규화 양식

정규화 작업에는 두 가지 종류가 있습니다.

  • 구성: 다중 코드 포인트 문자를 단일 코드 포인트로 변환합니다. 예:"a\u0300" "\u00E0"이(가) 됩니다. , 둘 다 à 문자를 인코딩하는 방법입니다. .
  • 분해: 구성의 반대. 단일 코드 포인트 문자를 여러 코드 포인트로 변환합니다. 예:"\u00E0" "a\u0300"가 됩니다. .

구성과 분해는 각각 두 가지 방법으로 수행할 수 있습니다.

  • 표준: 글리프를 유지합니다. 예:"2⁵" 남아 "2⁵" 일부 시스템에서는 위 첨자 5자를 지원하지 않을 수 있습니다.
  • 호환성: 글리프를 호환 가능한 문자로 바꿀 수 있습니다. 예:"2⁵" "2 5"로 변환됩니다. .

두 가지 작업과 두 가지 옵션을 다양한 방식으로 결합하여 네 가지 "정규화 형식"을 만듭니다. 입력 및 출력에 대한 설명 및 예와 함께 아래 표에 모두 나열했습니다.

이름 설명 입력 출력
NFD 정규 분해 Å "\u00c5" Å "A\u030A"
NFC 정규 구성에 따른 정준 분해 Å "A\u030A" Å "\u00c5"
NFKD 호환성 분해 ẛ̣ "\u1e9b\u0323" "\u0073\u0323\u0307"
NFKC 호환성 분해 후 정식 구성 ẛ̣ "\u1e9b\u0323" "\u1e69"

이 표를 몇 분 동안 살펴보면 두문자어가 의미가 있음을 알 수 있습니다.

  • "NF"는 "정규화 형식"을 나타냅니다.
  • "D"는 "분해"를 나타냅니다.
  • "C"는 "구성"을 나타냅니다.
  • "K"는 "호환성"을 나타냅니다. :)

더 많은 예와 훨씬 더 철저한 기술 설명은 Unicode Standard Annex #15를 확인하십시오.

정규화 형식 선택

사용해야 하는 정규화 형식은 당면한 작업에 따라 다릅니다. 아래의 권장 사항은 유니코드 정규화 FAQ를 기반으로 합니다.

문자열 호환성을 위해 NFC 사용

Ruby의 문자열 메서드가 대부분의 유니코드와 잘 작동하도록 하는 것이 목표라면 NFC를 사용하고 싶을 가능성이 큽니다. String#unicode_normalize의 기본값인 이유가 있습니다. .

  • 가능한 경우 다중 코드 포인트 문자를 단일 코드 포인트로 구성합니다. 다중 코드 포인트 문자는 String 메서드에서 대부분의 문제의 원인입니다.
  • 글리프를 변경하지 않으므로 최종 사용자는 입력한 텍스트의 변경 사항을 눈치채지 못할 것입니다.

즉, 모든 다중 코드 포인트 문자가 단일 코드 포인트로 구성될 수 있는 것은 아닙니다. 이러한 경우 Ruby의 String 메서드는 제대로 작동하지 않습니다.

s = "\u01B5\u0327\u0308"          # => "Ƶ̧̈", an un-composable character
s.unicode_normalize(:nfc).size    # => 3, even though there's only one character

보안 및 DB 호환성을 위해 NFKC 사용

사용자 이름과 같은 보안 관련 텍스트로 작업하거나 주로 텍스트가 데이터베이스에서 원활하게 재생되도록 하는 데 관심이 있다면 NFKC가 좋은 선택일 것입니다.

  • 잠재적으로 문제가 있는 문자를 호환 가능한 문자로 변환합니다.
  • 그런 다음 모든 문자를 단일 코드 포인트로 구성합니다.

이것이 보안에 유용한 이유를 보려면 사용자 이름이 "HenryIV"인 사용자가 있다고 상상해 보십시오. 악의적인 행위자가 새 사용자 이름 "HenryIV"를 등록하여 이 사용자를 가장하려고 할 수 있습니다.

나도 알아, 똑같이 생겼어. 그게 요점입니다. 그러나 그들은 실제로 두 개의 다른 문자열입니다. 전자는 ASCII 문자 "IV"를 사용합니다. 후자는 로마 숫자 4에 유니코드 문자를 사용합니다. "IV" .

고유성을 검증하기 전에 NFKC를 사용하여 문자열을 정규화하여 이러한 종류의 일을 방지할 수 있습니다. 이 경우 NFKC는 유니코드 "\u2163"를 변환합니다. 아스키 문자 "IV"로 변경합니다.

a = "Henry\u2163"
b = "HenryIV"
a.unicode_normalize(:nfc) == b.unicode_normalize(:nfc) # => false, because NFC preserves glyphs
a.unicode_normalize(:nfkc) == b.unicode_normalize(:nfkc) # => true, because NFKC evaluates both to the ascii "IV"

이별의 말

이제 더 자세히 살펴보았으므로 유니코드 정규화가 Ruby 및 Rails 커뮤니티에서 더 큰 주제가 아니라는 사실에 조금 놀랐습니다. Rails에 의해 수행될 것으로 예상할 수 있지만 내가 말할 수 있는 한 그렇지 않습니다. 사용자가 제공한 데이터를 정규화하지 않는다는 것은 Ruby의 많은 문자열 메서드가 신뢰할 수 없다는 것을 의미합니다.

친애하는 독자 중 내가 모르는 것을 알고 있는 경우 트위터 @StarrHorne을 통해 연락하거나 starr@honeybadger.io로 이메일을 보내주십시오. 유니코드는 큰 주제이고 내가 그것에 대해 모든 것을 모른다는 것을 이미 증명했습니다. :)