pypa/pip · error · IDNAError

Empty Label

Error message

Empty Label

What it means

IDNAError raised by check_label when, after bytes-to-str decoding, the label has length 0. An empty label is structurally invalid: DNS does not permit zero-length labels inside a name (only the trailing root label, represented separately).

Source

Thrown at src/pip/_vendor/idna/core.py:345

    (:func:`check_initial_combiner`), per-codepoint validity (PVALID,
    CONTEXTJ, CONTEXTO classes from :rfc:`5892`), and the Bidi Rule
    (:func:`check_bidi`).

    :param label: The label to validate. ``bytes`` or ``bytearray`` input
        is decoded as UTF-8 first.
    :raises IDNAError: If the label is empty or fails a structural rule.
    :raises InvalidCodepoint: If the label contains a DISALLOWED or
        UNASSIGNED codepoint.
    :raises InvalidCodepointContext: If a CONTEXTJ or CONTEXTO codepoint
        is not valid in its context.
    :raises IDNABidiError: If the Bidi Rule is violated.
    """
    if len(label) > _max_input_length:
        raise IDNAError("Label too long")
    if isinstance(label, (bytes, bytearray)):
        label = label.decode("utf-8")
    if len(label) == 0:
        raise IDNAError("Empty Label")

    # Reject on domain length rather than label length so support some UTS 46
    # use cases, still reducing processing of label contextual rules
    if not valid_string_length(label, trailing_dot=True):
        raise IDNAError("Label too long")

    check_nfc(label)
    check_hyphen_ok(label)
    check_initial_combiner(label)

    for pos, cp in enumerate(label):
        cp_value = ord(cp)
        if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]):
            continue
        if intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]):
            try:
                if not valid_contextj(label, pos):
                    raise InvalidCodepointContext(f"Joiner {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}")

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Reject empty labels at the input boundary before IDNA.
  2. Collapse consecutive dots / strip trailing dots before splitting: re.sub(r'\.+', '.', s).rstrip('.').
  3. Treat empty input as an application-level error with a clear message rather than passing to idna.

Example fix

# before
parts = 'a..b'.split('.')
for p in parts: idna.check_label(p)  # '' -> Empty Label

# after
parts = [p for p in 'a..b'.split('.') if p]
for p in parts: idna.check_label(p)
Defensive patterns

Strategy: validation

Validate before calling

def reject_empty_labels(domain: str) -> list:
    parts = [p for p in domain.split('.') if p != '']
    if not parts:
        raise ValueError('empty domain')
    return parts

Type guard

def has_no_empty_labels(domain: str) -> bool:
    return all(p != '' for p in domain.split('.')) and domain != ''

Try / catch

from idna import IDNAError
try:
    idna.check_label(label)
except IDNAError as e:
    if 'Empty' in str(e):
        # skip this label rather than aborting the whole domain
        return None
    raise

Prevention

When it happens

Trigger: Calling check_label/alabel with '' or b''; also reached when a domain like 'a..b' is split and an interior empty segment is passed to alabel (encode() guards this earlier with 'Empty label', but direct check_label callers see 'Empty Label').

Common situations: Double dots in user-typed hostnames ('example..com'); trailing-dot handling that leaves an empty segment; concatenation producing 'a.'+''; sanitizers that strip everything leaving ''.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/0b1d8fd1533477b7.json. Report an issue: GitHub.