package ident import ( "bytes" "errors" "fmt" "strconv" ) // ldh are bytes that may appear in a DNS label of Remote. var ldh = [1 << 8]bool{ '-': true, // special: must not be the first or last byte '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true, 'Y': true, 'Z': true, 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, 'y': true, 'z': true, } const ( // RemoteMax is the maximum permissible size of [Remote]. RemoteMax = 1<<8 - 1 // LabelSeparator separates labels in a [Remote]. LabelSeparator = '.' // LabelMax is the maximum permissible size of a label in [Remote]. LabelMax = 1<<6 - 1 ) // Remote represents the remote part of [F]. // // A remote must contain one or more non-empty labels, separated by // [LabelSeparator]. Each label must be no more than [LabelMax] bytes long, and // contain only bytes permitted by the "LDH rule" described in // [Section 2 of RFC 3696]. A remote must, in total, be no more than [RemoteMax] // bytes long. // // [Section 2 of RFC 3696]: https://www.rfc-editor.org/rfc/rfc3696#section-2 type Remote struct { Data [RemoteMax + 1]byte Len uint8 } // Encode appends the underlying string to dst and returns the extended buffer. func (r *Remote) Encode(dst []byte) []byte { return append(dst, r.Data[:r.Len]...) } // String returns a copy of the underlying string. func (r *Remote) String() string { return string(r.Data[:r.Len]) } // MarshalText returns the result of Encode. func (r *Remote) MarshalText() (data []byte, err error) { return r.Encode(nil), nil } var ( // ErrShortRemote is returned validating an empty remote. ErrShortRemote = errors.New("missing remote part") // ErrLongRemote is returned validating a remote longer than [RemoteMax]. ErrLongRemote = errors.New("got more than 255 bytes of remote") ) const ( // InvalidLabelLDH denotes an [InvalidLabelError] describing an invalid // label containing bytes not permitted by the LDH rule. InvalidLabelLDH = iota // InvalidLabelShort denotes an [InvalidLabelError] describing an empty label. InvalidLabelShort // InvalidLabelLong denotes an [InvalidLabelError] describing a label // exceeding its maximum length of [LabelMax]. InvalidLabelLong ) // An InvalidLabelError describes a rejected DNS label part of a [Remote]. type InvalidLabelError struct { Data []byte Label int Index int Reason int } func (e *InvalidLabelError) Error() string { s := "label " + strconv.Itoa(e.Label) + " " switch e.Reason { case InvalidLabelLDH: if e.Data[e.Index] == '-' { if e.Index == 0 { s += "starts" } else { s += "ends" } s += " with '-'" break } s += fmt.Sprintf( "contains invalid byte %q at index %d", e.Data[e.Index], e.Index, ) case InvalidLabelShort: s += "is empty" case InvalidLabelLong: s += "is longer than 63 bytes" default: return "invalid label " + strconv.Itoa(e.Label) + " at byte " + strconv.Itoa(e.Index) } return s } // UnmarshalText validates and copies data to r. func (r *Remote) UnmarshalText(data []byte) error { if len(data) == 0 { return ErrShortRemote } r.Len = uint8(len(data)) if copy(r.Data[:], data) == len(r.Data) { return ErrLongRemote } for i, label := range bytes.Split(r.Data[:r.Len], []byte{LabelSeparator}) { if len(label) == 0 { return &InvalidLabelError{label, i, -1, InvalidLabelShort} } else if len(label) > LabelMax { return &InvalidLabelError{label, i, LabelMax, InvalidLabelLong} } if label[0] == '-' { return &InvalidLabelError{label, i, 0, InvalidLabelLDH} } else if label[len(label)-1] == '-' { return &InvalidLabelError{label, i, len(label) - 1, InvalidLabelLDH} } for j, b := range label { if !ldh[b] { return &InvalidLabelError{label, i, j, InvalidLabelLDH} } } } return nil }