idn 0.1.1.0 → 0.1.2.0
raw patch · 4 files changed
+28/−22 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- idn.cabal +1/−1
- src/Data/Text/IDN.hs +17/−17
- src/Data/Text/IDN/Types.hs +7/−2
- test/Data/Text/IDNSpec.hs +3/−2
idn.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: idn-version: 0.1.1.0+version: 0.1.2.0 synopsis: Pure Haskell IDN and Punycode implementation description: Pure Haskell implementation of Internationalized Domain Names (IDN)
src/Data/Text/IDN.hs view
@@ -50,14 +50,14 @@ -- -- Converts each label in the domain name to its ASCII-compatible form. -- Unicode labels are encoded using Punycode and prefixed with "xn--".--- ASCII labels are validated and passed through unchanged.+-- ASCII labels are normalized to lowercase (case-insensitive per DNS). -- -- === Examples -- -- >>> toASCII "münchen.de" -- Right "xn--mnchen-3ya.de" ----- >>> toASCII "example.com"+-- >>> toASCII "EXAMPLE.COM" -- Right "example.com" -- -- === Errors@@ -113,6 +113,7 @@ -- | Process a single label to ASCII form. processLabelToASCII :: Label -> Either IDNError Label processLabelToASCII (ALabel t)+ -- Labels are already normalized to lowercase by mkLabel | "xn--" `T.isPrefixOf` t = do -- It's a Punycode A-label, validate it by decoding and re-encoding -- This uses processLabelToUnicode which validates Punycode properly@@ -120,12 +121,12 @@ -- Then re-process back to ASCII to get a validated A-label processLabelToASCII ulabel | otherwise =- -- Regular ASCII label, just validate+ -- Regular ASCII label (already normalized), just validate validateLabel t >> return (ALabel t)- + processLabelToASCII (ULabel t) | isAsciiLabel t = do- -- Pure ASCII label, validate and return as-is+ -- Pure ASCII label (already normalized), just validate validateLabel t return (ULabel t) @@ -144,22 +145,19 @@ -- | Process a single label to Unicode form. processLabelToUnicode :: Label -> Either IDNError Label-processLabelToUnicode (ULabel t) = +processLabelToUnicode (ULabel t) = -- Already Unicode, validate and return validateLabel t >> return (ULabel t)- + processLabelToUnicode (ALabel t)- | "xn--" `T.isPrefixOf` t = do+ -- RFC 5891 Section 5.1: A-labels are case-insensitive for matching+ | "xn--" `T.isPrefixOf` T.toLower t = do -- Punycode A-label, decode it- -- RFC 5891 Section 5.4: A-labels must be lowercase+ -- RFC 5891 Section 5.1: Accept case-insensitive input, normalize to lowercase+ -- Drop the prefix (which might be XN--, Xn--, xN--, or xn--) let encoded = T.drop 4 t encodedLower = T.toLower encoded - -- Check if original was already lowercase (uppercase in Punycode is invalid)- if encoded /= encodedLower- then Left (PunycodeDecodeError encoded "A-label must be lowercase")- else pure ()- decoded <- case decodePunycode encodedLower of Left err -> Left (PunycodeDecodeError encoded (T.pack (show err))) Right d -> Right d@@ -246,13 +244,15 @@ case T.uncons label of Just ('-', _) -> Left (InvalidHyphenPosition StartsWithHyphen) _ -> pure ()- + case T.unsnoc label of Just (_, '-') -> Left (InvalidHyphenPosition EndsWithHyphen) _ -> pure ()- + -- Check for "--" in positions 3-4 (unless it's an A-label with xn--)- if T.length label >= 4 && not ("xn--" `T.isPrefixOf` label)+ -- RFC 5891: A-labels are case-insensitive for matching+ let labelLower = T.toLower label+ if T.length label >= 4 && not ("xn--" `T.isPrefixOf` labelLower) then if T.index label 2 == '-' && T.index label 3 == '-' then Left (InvalidHyphenPosition HyphensAt3And4NotPunycode) else pure ()
src/Data/Text/IDN/Types.hs view
@@ -185,8 +185,13 @@ mkLabel text | T.null text = Left EmptyLabel | T.length text > 63 = Left (LabelTooLong (T.length text) 63)- | "xn--" `T.isPrefixOf` text = Right (ALabel text)- | otherwise = Right (ULabel text)+ | otherwise =+ -- RFC 5891/3490: Domain labels are case-insensitive, normalize to lowercase+ let normalized = T.toLower text+ -- RFC 5891 Section 5.1: A-labels start with "xn--"+ in if "xn--" `T.isPrefixOf` normalized+ then Right (ALabel normalized)+ else Right (ULabel normalized) -- | Smart constructor for 'DomainName' with invariant enforcement. --
test/Data/Text/IDNSpec.hs view
@@ -54,8 +54,9 @@ toASCII "example-.com" `shouldSatisfy` isLeft toASCII "ab--cd.com" `shouldSatisfy` isLeft - it "accepts valid xn-- Punycode prefix" $ do- toASCII "xn--test.com" `shouldSatisfy` isRight+ it "rejects xn-- prefix with pure ASCII (RFC 5891 violation)" $ do+ -- RFC 5891: Punycode should only be used for labels with non-ASCII characters+ toASCII "xn--test.com" `shouldSatisfy` isLeft it "handles multi-label domains" $ do toASCII "sub.example.com" `shouldBe` Right "sub.example.com"