packages feed

idna2008-1.0.0.0: internal/Text/IDNA2008/Internal/Error.hs

-- |
-- Module      : Text.IDNA2008.Internal.Error
-- Description : Error type returned by the parser.
-- Copyright   : (c) Viktor Dukhovni, 2026
-- License     : BSD-3-Clause
--
-- Maintainer  : ietf-dane@dukhovni.org
-- Stability   : unstable
--
-- Errors raised by the IDNA-aware domain-name parser.  Failures
-- are reported with enough context (label index, byte offset
-- where useful) for callers to pinpoint the offending part of
-- the input without the library having to retain or echo back
-- the raw input bytes.
module Text.IDNA2008.Internal.Error
    ( IdnaError(..)
    , LabelReason(..)
    , AceReason(..)
    , BidiRuleViolation(..)
    ) where

import Text.IDNA2008.Internal.LabelForm (LabelForm)

-- | Possible failure modes when parsing a presentation-form
-- domain name into wire form, including IDN encoding /
-- decoding errors.
data IdnaError
    = -- | An empty (non-final) label was encountered, e.g. @\"foo..bar\"@.
      ErrEmptyLabel
        !Int -- ^ Label index (0-based)
    | -- | A label exceeds 63 wire octets.  The 'Int' is the actual length.
      ErrLabelTooLong
        !Int -- ^ Label index
        !Int -- ^ Actual length
    | -- | The domain's total wire length would exceed 255 bytes.
      ErrNameTooLong
        !Int -- ^ Actual length.
    | -- | A backslash was followed by an invalid escape sequence.
      ErrBadEscape
        !Int -- ^ Label index
        !(Maybe Int) -- ^ The zero-based byte offset into the original input
                     -- where the offending escape begins, when known.
    | -- | The input contained an ill-formed UTF-8 byte sequence.
      ErrInvalidUtf8
        !Int -- ^ Label index
        !(Maybe Int) -- ^ The byte offset into the original input where the
                     -- ill-formed byte sequence begins, when known.  The label
                     -- index is often @-1@ because the error is detected
                     -- before any label boundary is recognised.
    | -- | The input includes a label that has no coherent presentation form: it
      -- mixes raw-byte escape syntax (@\'\\DDD\'@ or @\'\\X\'@, only allowed
      -- in @OCTET@ labels) with a codepoint outside the 8-bit range (which an
      -- @OCTET@ label cannot carry).
      ErrUnpresentableLabel
        !Int -- ^ Label index
    | -- | A codepoint outside the legal Unicode range (above @U+10FFFF@) or
      -- in the surrogate range was observed.
      ErrCodepointTooLarge
        !Int -- ^ Label index.
        !Int -- ^ Codepoint.
    | -- | The label was successfully classified, but its 'LabelForm' is not
      -- in the caller-supplied set of permitted forms.
      ErrFormNotAllowed
        !Int -- ^ Label index.
        !LabelForm -- ^ The form of the rejected label.
    | -- | A label that took the validation path failed it; see
      ErrLabelInvalid
        !Int -- ^ Label index.
        !LabelReason -- ^ The specific cause.
    | -- | A label looked like an ACE label (@\"xn--...\"@) but
      -- failed validation: Punycode decode failed, decoded form
      -- is not a valid IDN label, or re-encoding does not match.
      ErrAceInvalid
        !Int -- ^ Label index.
        !AceReason -- ^ Reason
    | -- | The label violated a cross-label bidirectional-text rule.
      -- Raised when one or more labels in the name contains right-to-left
      -- content; the rules then apply to /every/ label in the
      -- name, including pure left-to-right siblings.
      ErrCrossLabelBidi
        !Int -- ^ Label index.
        !BidiRuleViolation -- ^ Actual problem
    | -- | Internal Punycode arithmetic overflow during encode or
      -- decode.  Should not occur for compliant inputs of bounded
      -- length.
      ErrPunycodeOverflow
        !Int -- ^ Label index.
    deriving (Eq, Show)

-- | Why a label failed validation.
data LabelReason
    = DisallowedCodepoint !Int
      -- ^ A codepoint not permitted in any IDN label.  Carries
      -- the offending codepoint regardless of whether it's
      -- ASCII (e.g. an uppercase letter, an underscore) or
      -- non-ASCII (a symbol, dingbat, etc.).
    | ContextRule !Int
      -- ^ A codepoint admissible only in specific contexts (a
      -- joiner, an Arabic-Indic digit, etc.) appeared in a
      -- context where its rule isn't satisfied.
    | NotNFC
      -- ^ The label is not in Unicode Normalization Form C.
    | LabelBidi !BidiRuleViolation
      -- ^ A bidirectional-text rule was violated within the
      -- label.  See 'BidiRuleViolation' for the specific rule.
    | HyphenViolation
      -- ^ Per RFC 5891 section 4.2.3.1: the label has a leading
      -- hyphen, a trailing hyphen, or hyphens at both positions
      -- 3 and 4.  The latter form reserves those slots for the
      -- ACE prefix (@\"xn--\"@), so a U-label that contains
      -- @\"--\"@ at positions 3-4 would visually mimic an
      -- A-label without being one and is rejected.
    | LeadingCombiningMark !Int
      -- ^ The first codepoint of the label has
      -- @General_Category@ in @{Mn, Mc, Me}@.  Per RFC 5891
      -- section 4.2.3.2, a U-label must not begin with a
      -- combining mark.  Carries the offending codepoint.
    deriving (Eq, Show)

-- | Why an ACE-prefixed (@\"xn--\"@) label failed validation.
data AceReason
    = BadPunycode
      -- ^ Punycode body could not be decoded (truncated, bad
      -- delimiter, non-base-36 digit).
    | DecodedInvalid !LabelReason
      -- ^ Decoding succeeded but the result is not a valid IDN
      -- label.  See 'LabelReason' for the specific cause.
    | RoundTripMismatch
      -- ^ Decoded label re-encoded to a form that does not match
      -- the input ACE bytes.  The round-trip check is
      -- case-insensitive.
    deriving (Eq, Show)

-- | Which bidirectional-text rule a label violates, with enough
-- granularity for a caller to format a useful diagnostic
-- without having to consult the rule text.  Used as the
-- payload of 'LabelBidi' (per-label check) and
-- 'ErrCrossLabelBidi' (cross-label check).
--
-- The rules apply when a label contains right-to-left text
-- (Hebrew, Arabic, ...) or an Arabic-Indic digit.
data BidiRuleViolation
    = BidiRule1FirstNotLRAL
      -- ^ The first codepoint of a bidirectional label
      -- was not a letter from a known left-to-right or
      -- right-to-left script.  Most often catches digit-, mark-,
      -- or punctuation-leading labels in a name that also has a
      -- right-to-left component (e.g. @\"_tcp\"@, @\"123\"@).
    | BidiRule2RTLDisallowed
      -- ^ A right-to-left label (one whose first codepoint is a
      -- right-to-left letter) contained a codepoint that is not
      -- allowed in such a label — typically a left-to-right
      -- letter, or an embedding\/override\/isolate format
      -- character.
    | BidiRule3RTLBadEnd
      -- ^ A right-to-left label's last non-mark codepoint was
      -- not a letter or digit.
    | BidiRule4ENANMix
      -- ^ A right-to-left label mixed European-style digits
      -- (ASCII or extended Arabic-Indic) with Arabic-Indic
      -- digits.  The two digit families cannot coexist in the
      -- same label.
    | BidiRule5LTRDisallowed
      -- ^ A left-to-right label (one whose first codepoint is a
      -- left-to-right letter) contained a codepoint that is not
      -- allowed in such a label — typically a right-to-left
      -- letter or an Arabic-Indic digit.
    | BidiRule6LTRBadEnd
      -- ^ A left-to-right label's last non-mark codepoint was
      -- not a letter or a European-style digit.
    deriving (Eq, Show)